mirror of
https://github.com/qelectrotech/qelectrotech-source-mirror.git
synced 2026-09-30 23:04:13 +02:00
Add Ctrl+D: duplicate the selection, offset by a configured grid step (#991)
Split from #913's second suggestion. There was no shortcut for the common "duplicate with offset" convention; the nearest existing feature, "Collage multiple", is a different workflow (a dialog for repeating a paste in a grid pattern, not a one-shot duplicate). Ctrl+D copies the selection and places it immediately, offset by a configured spacing and direction -- no interactive follow-the-cursor step, unlike Ctrl+V. The first press (or after the setting is explicitly reopened) shows DuplicateOffsetDialog: spacing in grid steps, direction up/down/left/right. Every later press reuses whatever was confirmed then, silently, so a row of copies is one key held down and tapped, not a dialog every time -- unattended, repeatable stamping is the actual point of a duplicate shortcut, which a dialog or an interactive placement step on every press would defeat. A separate "Configurer la duplication..." entry reopens the dialog on demand to change the setting later. Cancel leaves the diagram untouched -- verified, not assumed: qet_diff against the saved file shows 0 added. Chaining ("keep tapping to lay out a row") needs no special handling: QET already reselects whatever a paste just added (PasteDiagramCommand::redo()), so the next Ctrl+D naturally continues from the copy just placed rather than the original. The offset is applied by hand rather than by asking paste()/fromXml() to place the copy at a target position. Both of those feed the position through Diagram::snapToGrid(), which reads QApplication::keyboardModifiers() and rounds to the nearest PIXEL instead of the grid whenever Ctrl is held -- and Ctrl is always held here, this action's own shortcut being Ctrl+D. Measured before settling on this: routing the offset through paste() first produced copies off-grid on both axes, by an amount that tracked the selection's own bounding-box geometry rather than being a fixed error -- caught by qet-mcp's qet_elements against the saved file, not by eye. fromXml() is instead called with no position argument at all (leaves every item at its source coordinates, landing the copy on top of the originals -- (0,0) is not a position, this is "keep the source coordinates"), and the offset is added directly with setPos(). A plain addition cannot be off by a rounding rule that never runs. Conductors are not in the hand-translated set: fromXml() itself does not reposition them either -- they load after elements are already in their final place and take their geometry from their terminals, which have already moved with the elements that own them. Verified this holds: drew a conductor by hand between two elements (drag, not click-click), selected both, Ctrl+D, and the new conductor correctly joins the two new elements via qet_conductors -- not the originals, not a mix. Verified end-to-end on a built binary via qet-mcp, not by eye: before L2 (303,207) L9 (512,196) -- deliberately off-grid spacing=2, down (303,227) (512,216) -- +0,+20 exactly same again, 2nd (303,247) (512,236) -- +0,+20 again, chained Both elements land exactly the configured offset from their immediate source regardless of the selection's own alignment. Qt 6.10.2, ctest 11/11. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -445,6 +445,82 @@ void DiagramView::pasteHere()
|
||||
paste(mapToScene(m_paste_here_pos));
|
||||
}
|
||||
|
||||
/**
|
||||
@brief DiagramView::duplicate
|
||||
Copy the current selection and place the copy at @p stepOffset grid
|
||||
steps from it, landing immediately rather than following the cursor
|
||||
like Ctrl+V does (bugtracker #991). @p stepOffset comes from
|
||||
DuplicateOffsetDialog: (1, 0) is one grid step right, (0, -1) is one
|
||||
grid step up, and so on -- QET's own scene axes, X right and Y down.
|
||||
|
||||
No interactive placement step on purpose: the point of a duplicate
|
||||
shortcut is unattended, repeatable stamping (configure the offset
|
||||
once, then tap Ctrl+D to lay out a row), which following the cursor
|
||||
would interrupt on every press. QET already reselects whatever a
|
||||
paste just added (see PasteDiagramCommand::redo()), so the next
|
||||
Ctrl+D naturally continues from the copy just placed, not the
|
||||
original -- a press-and-hold row falls out of that for free, with no
|
||||
special-casing needed here for "keep going from the last one".
|
||||
|
||||
The offset is applied by hand rather than by asking paste()/
|
||||
Diagram::fromXml() to place the copy at a target position. Both of
|
||||
those feed the position through Diagram::snapToGrid(), which reads
|
||||
QApplication::keyboardModifiers() and rounds to the nearest PIXEL
|
||||
instead of the grid whenever Ctrl is held -- and Ctrl is always held
|
||||
here, this action's own shortcut being Ctrl+D. Measured the hard way
|
||||
before settling on this: routing the offset through paste() first
|
||||
produced copies off-grid on both axes, by an amount that tracked the
|
||||
selection's own bounding-box geometry rather than being a fixed
|
||||
error. fromXml() is instead called with no position at all, which
|
||||
leaves every item at its source coordinates (landing the copy
|
||||
exactly on top of the originals -- (0, 0) is not a position, this is
|
||||
"keep the source coordinates"), and the offset is added directly
|
||||
with setPos(). A plain addition cannot be off by a rounding rule
|
||||
that never runs.
|
||||
|
||||
Conductors are not in the translated set: fromXml() itself does not
|
||||
reposition them either -- they are loaded from XML after elements
|
||||
are already in their final place and take their geometry from their
|
||||
terminals, which have already moved with the elements that own
|
||||
them. Likewise dynamic element texts are not translated separately:
|
||||
they are children of their element and move with it under Qt's
|
||||
normal parent-child transform.
|
||||
*/
|
||||
void DiagramView::duplicate(const QPoint &stepOffset)
|
||||
{
|
||||
if (!isInteractive() || m_diagram->isReadOnly()) return;
|
||||
|
||||
const QList<QGraphicsItem *> selection = m_diagram->selectedItems();
|
||||
if (selection.isEmpty()) return;
|
||||
|
||||
QSettings settings;
|
||||
const int x_grid = settings.value(QStringLiteral("diagrameditor/Xgrid"),
|
||||
Diagram::xGrid).toInt();
|
||||
const int y_grid = settings.value(QStringLiteral("diagrameditor/Ygrid"),
|
||||
Diagram::yGrid).toInt();
|
||||
const QPointF offset(stepOffset.x() * x_grid, stepOffset.y() * y_grid);
|
||||
|
||||
// Mirrors copy(), but does not touch the system clipboard: Ctrl+D
|
||||
// should not clobber whatever the user last copied with Ctrl+C.
|
||||
QDomDocument document = m_diagram->toXml(false, true);
|
||||
|
||||
DiagramContent pasted;
|
||||
// No position argument -- see the function comment above for why
|
||||
// the offset is not passed here.
|
||||
m_diagram->fromXml(document, QPointF(), false, &pasted);
|
||||
if (!pasted.count()) return;
|
||||
|
||||
const int movable = DiagramContent::Elements | DiagramContent::TextFields
|
||||
| DiagramContent::Images | DiagramContent::Shapes
|
||||
| DiagramContent::Tables | DiagramContent::TerminalStrip;
|
||||
for (QGraphicsItem *item : pasted.items(movable))
|
||||
item->setPos(item->pos() + offset);
|
||||
|
||||
m_diagram->clearSelection();
|
||||
m_diagram->undoStack().push(new PasteDiagramCommand(m_diagram, pasted));
|
||||
adjustSceneRect();
|
||||
}
|
||||
|
||||
/**
|
||||
Manage the events press click :
|
||||
* click to add an independent text field
|
||||
|
||||
Reference in New Issue
Block a user