mirror of
https://github.com/qelectrotech/qelectrotech-source-mirror.git
synced 2026-10-02 08:24:14 +02:00
Ctrl+Shift+V pastes at the origin point, Ctrl+V keeps pasting under the cursor
Ctrl+V moves the pasted group under the pointer as before. The new
Ctrl+Shift+V ("Coller au point d'origine", diagrameditor.paste_origin)
leaves the group exactly where it was copied from and warps the OS
cursor to the group's grid-snapped origin, so the paste baseline and
the physical cursor agree and the group starts following the mouse from
its original place instead of jumping to the pointer.
The cursor warp is only trusted as the baseline when it verifiably
lands (QCursor::pos() agrees with the target): QCursor::setPos() needs
pointer focus and is a silent no-op on compositors without
wp_pointer_warp_v1. Otherwise m_baseline_captured stays false and
moveTo() re-baselines on the first real mouse move, so a refused warp
costs at most the first movement rather than flinging the group across
the folio. An origin outside the viewport is scrolled into view first,
since the compositor rejects warp targets outside the window.
This commit is contained in:
@@ -28,7 +28,9 @@
|
||||
|
||||
#include <QApplication>
|
||||
#include <QClipboard>
|
||||
#include <QCursor>
|
||||
#include <QGraphicsSceneMouseEvent>
|
||||
#include <QGraphicsView>
|
||||
#include <QKeyEvent>
|
||||
#include <QStatusBar>
|
||||
|
||||
@@ -36,11 +38,18 @@
|
||||
@brief DiagramEventAddPaste::DiagramEventAddPaste
|
||||
@param diagram : diagram to paste into
|
||||
@param start_pos : where the pasted items first appear, in scene
|
||||
coordinates -- normally the cursor
|
||||
coordinates -- normally the cursor. Only used for UnderCursor; an
|
||||
AtOrigin paste stays at the coordinates it was copied from.
|
||||
@param placement : UnderCursor moves the group under the cursor
|
||||
(default), AtOrigin leaves it at its original XML position and warps
|
||||
the OS cursor to the group's origin so baseline and screen match.
|
||||
*/
|
||||
DiagramEventAddPaste::DiagramEventAddPaste(Diagram *diagram, const QPointF &start_pos) :
|
||||
DiagramEventAddPaste::DiagramEventAddPaste(Diagram *diagram, const QPointF &start_pos,
|
||||
PastePlacement placement) :
|
||||
DiagramEventInterface(diagram)
|
||||
{
|
||||
m_placement = placement;
|
||||
|
||||
//DiagramEventInterface::init() is called by Diagram::setEventInterface
|
||||
//only when it is replacing an earlier interface, so call it here as
|
||||
//DiagramEventAddMacro does.
|
||||
@@ -106,21 +115,31 @@
|
||||
qRound(p.x() / xGrid) * xGrid,
|
||||
qRound(p.y() / yGrid) * yGrid);
|
||||
};
|
||||
const QPointF grid_origin = snapGrid(start_pos);
|
||||
const QPointF grid_origin = (m_placement == AtOrigin)
|
||||
? snapGrid(top_left)
|
||||
: snapGrid(start_pos);
|
||||
|
||||
//Land the pasted content under the cursor immediately, rather than
|
||||
//leaving it at the copied source's own coordinates: fromXml() above
|
||||
//loads items at their original position purely because it doesn't
|
||||
//know the target yet, not because that is where a paste should end
|
||||
//up. The previous approach instead left items there and warped the
|
||||
//OS cursor to match -- QCursor::setPos() is silently ignored by
|
||||
//many window managers and compositors (Wayland in particular), so
|
||||
//on any of those the warp simply never happened and the paste was
|
||||
//left wherever it had originally been copied from, which could be
|
||||
//anywhere on the folio -- exactly the "far from the cursor" bug.
|
||||
const QPointF initial_delta = grid_origin - snapGrid(top_left);
|
||||
for (auto *item : movable) {
|
||||
item->setPos(item->pos() + initial_delta);
|
||||
if (m_placement == UnderCursor) {
|
||||
//Land the pasted content under the cursor immediately, rather than
|
||||
//leaving it at the copied source's own coordinates: fromXml() above
|
||||
//loads items at their original position purely because it doesn't
|
||||
//know the target yet, not because that is where a paste should end
|
||||
//up. The previous approach instead left items there and warped the
|
||||
//OS cursor to match -- QCursor::setPos() is silently ignored by
|
||||
//many window managers and compositors (Wayland in particular), so
|
||||
//on any of those the warp simply never happened and the paste was
|
||||
//left wherever it had originally been copied from, which could be
|
||||
//anywhere on the folio -- exactly the "far from the cursor" bug.
|
||||
const QPointF initial_delta = grid_origin - snapGrid(top_left);
|
||||
for (auto *item : movable) {
|
||||
item->setPos(item->pos() + initial_delta);
|
||||
}
|
||||
} else {
|
||||
//AtOrigin: the items keep the coordinates they were copied from,
|
||||
//so the copy stands exactly where the original stands. No delta is
|
||||
//applied; what has to move is the cursor -- it is warped to the
|
||||
//group's grid-snapped origin below, once m_group_origin is set,
|
||||
//so the baseline and the physical cursor position agree.
|
||||
}
|
||||
|
||||
//Store each item's now-placed position. moveTo() applies a
|
||||
@@ -138,10 +157,41 @@
|
||||
conductor->updatePath();
|
||||
}
|
||||
|
||||
//The baseline is the group's grid-snapped origin, so moveTo()
|
||||
//does not have to capture one from the first mouse movement.
|
||||
m_initial_cursor = m_group_origin;
|
||||
m_baseline_captured = true;
|
||||
//The baseline is the group's grid-snapped origin. For UnderCursor
|
||||
//the group was just put there, so the baseline is known outright.
|
||||
//For AtOrigin the group stayed where it was copied from and the
|
||||
//OS cursor is warped to match instead -- but only a warp that
|
||||
//verifiably landed may be trusted as the baseline: QCursor::setPos()
|
||||
//needs pointer focus inside the target window (on Wayland it goes
|
||||
//through wp_pointer_warp_v1, and is a silent no-op without it), and
|
||||
//a baseline that assumes a warp which did not happen would move the
|
||||
//whole group by the warp-sized delta on the first mouse move. So
|
||||
//when the warp cannot be confirmed, leave m_baseline_captured
|
||||
//false: moveTo() re-baselines from the first real mouse position
|
||||
//instead, which costs at most the first movement but never flings
|
||||
//the group across the folio.
|
||||
if (m_placement == AtOrigin && !m_diagram->views().isEmpty()) {
|
||||
if (auto *view = m_diagram->views().at(0)) {
|
||||
//An origin outside the visible area cannot be warped to
|
||||
//(the compositor rejects targets outside the window) and
|
||||
//would leave the paste invisible, so scroll it into view
|
||||
//first.
|
||||
if (!view->viewport()->rect().contains(view->mapFromScene(m_group_origin))) {
|
||||
view->ensureVisible(items_rect, 50, 50);
|
||||
}
|
||||
const QPoint view_pos = view->mapFromScene(m_group_origin);
|
||||
const QPoint global_pos = view->viewport()->mapToGlobal(view_pos);
|
||||
QCursor::setPos(global_pos);
|
||||
if ((QCursor::pos() - global_pos).manhattanLength() <= 4) {
|
||||
m_initial_cursor = m_group_origin;
|
||||
m_baseline_captured = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (m_placement == UnderCursor) {
|
||||
m_initial_cursor = m_group_origin;
|
||||
m_baseline_captured = true;
|
||||
}
|
||||
|
||||
m_diagram->clearSelection();
|
||||
for (auto *item : movable) {
|
||||
|
||||
@@ -56,7 +56,21 @@ class DiagramEventAddPaste : public DiagramEventInterface
|
||||
| DiagramContent::Tables
|
||||
| DiagramContent::TerminalStrip;
|
||||
|
||||
DiagramEventAddPaste(Diagram *diagram, const QPointF &start_pos);
|
||||
///Where the group starts, relative to what was copied.
|
||||
enum PastePlacement {
|
||||
///Move the group so its top left lands at \a start_pos
|
||||
///(normally the cursor): Ctrl+V, the copy appears under
|
||||
///the pointer and follows it.
|
||||
UnderCursor,
|
||||
///Leave the group at its original XML position and warp
|
||||
///the OS cursor to the group's origin so the baseline
|
||||
///matches what is on screen: Ctrl+Shift+V, "paste at the
|
||||
///origin point".
|
||||
AtOrigin
|
||||
};
|
||||
|
||||
DiagramEventAddPaste(Diagram *diagram, const QPointF &start_pos,
|
||||
PastePlacement placement = UnderCursor);
|
||||
~DiagramEventAddPaste() override;
|
||||
|
||||
void mouseMoveEvent (QGraphicsSceneMouseEvent *event) override;
|
||||
@@ -79,14 +93,17 @@ class DiagramEventAddPaste : public DiagramEventInterface
|
||||
///Each movable item's position relative to the group's top left,
|
||||
///taken once so repeated moves cannot accumulate rounding drift.
|
||||
QHash<QGraphicsItem *, QPointF> m_relative_pos;
|
||||
///Where the group's grid-snapped top left was put when the paste
|
||||
///started, in scene coordinates -- the cursor, so the copy
|
||||
///appears under the pointer rather than on top of what was
|
||||
///copied.
|
||||
///Where this paste starts: UnderCursor moves the group to
|
||||
///start_pos, AtOrigin leaves it at its original XML position.
|
||||
PastePlacement m_placement{UnderCursor};
|
||||
///Where the group's grid-snapped top left sits when the paste
|
||||
///started, in scene coordinates -- under the cursor for
|
||||
///UnderCursor, the copy's own origin for AtOrigin.
|
||||
QPointF m_group_origin;
|
||||
///Cursor position (scene coords) the delta-based movement in
|
||||
///moveTo() measures from. Equal to m_group_origin, since the
|
||||
///group is placed at the cursor.
|
||||
///moveTo() measures from. Equal to m_group_origin: either the
|
||||
///group was put there (UnderCursor) or the cursor was warped
|
||||
///there (AtOrigin).
|
||||
QPointF m_initial_cursor;
|
||||
///Whether m_initial_cursor holds a usable baseline. A flag rather
|
||||
///than testing m_initial_cursor.isNull(), which cannot tell "not
|
||||
|
||||
@@ -359,14 +359,17 @@ void QETDiagramEditor::setUpActions()
|
||||
m_cut = new QAction(QET::Icons::EditCut, tr("Co&uper"), this);
|
||||
m_copy = new QAction(QET::Icons::EditCopy, tr("Cop&ier"), this);
|
||||
m_paste = new QAction(QET::Icons::EditPaste, tr("C&oller"), this);
|
||||
m_paste_origin = new QAction(QET::Icons::EditPaste, tr("Coller au point d'origine"), this);
|
||||
|
||||
ShortcutManager::instance().registerAction(m_cut, "diagrameditor.cut", tr("Éditeur de schémas"), QKeySequence::Cut);
|
||||
ShortcutManager::instance().registerAction(m_copy, "diagrameditor.copy", tr("Éditeur de schémas"), QKeySequence::Copy);
|
||||
ShortcutManager::instance().registerAction(m_paste, "diagrameditor.paste", tr("Éditeur de schémas"), QKeySequence::Paste);
|
||||
ShortcutManager::instance().registerAction(m_paste_origin, "diagrameditor.paste_origin", tr("Éditeur de schémas"), QKeySequence(Qt::CTRL | Qt::SHIFT | Qt::Key_V));
|
||||
|
||||
m_cut -> setStatusTip(tr("Transfère les éléments sélectionnés dans le presse-papier", "status bar tip"));
|
||||
m_copy -> setStatusTip(tr("Copie les éléments sélectionnés dans le presse-papier", "status bar tip"));
|
||||
m_paste -> setStatusTip(tr("Place les éléments du presse-papier sur le folio", "status bar tip"));
|
||||
m_paste_origin -> setStatusTip(tr("Place les éléments du presse-papier à leur position d'origine et déplace le curseur vers ce point", "status bar tip"));
|
||||
|
||||
connect(m_cut, &QAction::triggered, [this]() {
|
||||
if (currentDiagramView())
|
||||
@@ -376,7 +379,10 @@ void QETDiagramEditor::setUpActions()
|
||||
if (currentDiagramView())
|
||||
currentDiagramView()->copy();
|
||||
});
|
||||
connect(m_paste, &QAction::triggered, [this]() {
|
||||
|
||||
//Both paste shortcuts share one starter; they differ only in the
|
||||
//placement mode handed to DiagramEventAddPaste.
|
||||
const auto start_paste = [this](DiagramEventAddPaste::PastePlacement placement) {
|
||||
auto *dv = currentDiagramView();
|
||||
if (!dv || !dv->diagram()) return;
|
||||
|
||||
@@ -399,7 +405,13 @@ void QETDiagramEditor::setUpActions()
|
||||
const QPointF start_pos = dv->mapToScene(view_pos);
|
||||
|
||||
dv->diagram()->setEventInterface(
|
||||
new DiagramEventAddPaste(dv->diagram(), start_pos));
|
||||
new DiagramEventAddPaste(dv->diagram(), start_pos, placement));
|
||||
};
|
||||
connect(m_paste, &QAction::triggered, [start_paste]() {
|
||||
start_paste(DiagramEventAddPaste::UnderCursor);
|
||||
});
|
||||
connect(m_paste_origin, &QAction::triggered, [start_paste]() {
|
||||
start_paste(DiagramEventAddPaste::AtOrigin);
|
||||
});
|
||||
|
||||
//Duplicate: copy the selection and place it at a configured,
|
||||
@@ -1239,6 +1251,7 @@ void QETDiagramEditor::setUpMenu()
|
||||
menu_edition -> addAction(m_cut);
|
||||
menu_edition -> addAction(m_copy);
|
||||
menu_edition -> addAction(m_paste);
|
||||
menu_edition -> addAction(m_paste_origin);
|
||||
menu_edition -> addAction(m_duplicate);
|
||||
menu_edition -> addAction(m_configure_duplicate);
|
||||
menu_edition -> addAction(m_insert_last_element);
|
||||
@@ -2566,6 +2579,7 @@ void QETDiagramEditor::slot_updatePasteAction()
|
||||
|
||||
// pour coller, il faut un schema ouvert et un schema dans le presse-papier
|
||||
m_paste -> setEnabled(editable_diagram && Diagram::clipboardMayContainDiagram());
|
||||
m_paste_origin -> setEnabled(editable_diagram && Diagram::clipboardMayContainDiagram());
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -220,6 +220,7 @@ class QETDiagramEditor : public QETMainWindow
|
||||
*undo, ///< Cancel the latest action
|
||||
*redo, ///< Redo the latest cancelled operation
|
||||
*m_paste, ///< Paste clipboard content on the current diagram
|
||||
*m_paste_origin, ///< Same, at the copied position, cursor warped to the origin (Ctrl+Shift+V)
|
||||
*m_duplicate, ///< Copy selection, offset by the configured step (#991)
|
||||
*m_configure_duplicate, ///< Reopen the duplicate offset/direction dialog (#991)
|
||||
*m_auto_conductor, ///< Enable/Disable the use of auto conductor
|
||||
|
||||
Reference in New Issue
Block a user