mirror of
https://github.com/qelectrotech/qelectrotech-source-mirror.git
synced 2026-10-01 23:54:13 +02:00
Merge pull request #1208 from Kellermorph/strg-shift-v
Linux build and tests / Build and test (Qt 6, Debug) (push) Failing after 3m18s
Linux build and tests / Build and test (Qt 6, Debug) (push) Failing after 3m18s
Ctrl+Shift+V pastes at the origin point, Ctrl+V keeps pasting under the cursor
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,
|
||||
@@ -1247,6 +1259,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);
|
||||
@@ -2578,6 +2591,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