mirror of
https://github.com/qelectrotech/qelectrotech-source-mirror.git
synced 2026-09-26 03:44:14 +02:00
355 lines
13 KiB
C++
355 lines
13 KiB
C++
/*
|
|
Copyright 2006-2026 The QElectroTech Team
|
|
This file is part of QElectroTech.
|
|
|
|
QElectroTech is free software: you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation, either version 2 of the License, or
|
|
(at your option) any later version.
|
|
|
|
QElectroTech is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with QElectroTech. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include "diagrameventaddimage.h"
|
|
|
|
#include "../qetapp.h"
|
|
#include "../qetdiagrameditor.h"
|
|
#include "../diagram.h"
|
|
#include "../undocommand/addgraphicsobjectcommand.h"
|
|
#include "../qetgraphicsitem/diagramimageitem.h"
|
|
|
|
#include <QStatusBar>
|
|
#include <QTimer>
|
|
|
|
/**
|
|
@brief DiagramEventAddImage::DiagramEventAddImage
|
|
Default constructor
|
|
@param diagram : diagram where operate this event
|
|
*/
|
|
DiagramEventAddImage::DiagramEventAddImage(Diagram *diagram) :
|
|
DiagramEventInterface(diagram),
|
|
m_image (nullptr),
|
|
m_is_added (false)
|
|
{
|
|
openDialog();
|
|
if (m_running)
|
|
{
|
|
// Deferred for the same reason as the shape tools' own
|
|
// constructor-time hint: Diagram::setEventInterface() destroys
|
|
// whatever tool was previously active *after* this constructor
|
|
// returns, and that tool's own destructor clears the status bar
|
|
// -- an immediate show here would just get wiped out moments
|
|
// later by that cleanup.
|
|
QTimer::singleShot(0, this, [this]() { showHint(); });
|
|
}
|
|
}
|
|
|
|
/**
|
|
@brief DiagramEventAddImage::~DiagramEventAddImage
|
|
*/
|
|
DiagramEventAddImage::~DiagramEventAddImage()
|
|
{
|
|
if (m_running || m_abort)
|
|
{
|
|
if (m_is_added) m_diagram->removeItem(m_image);
|
|
delete m_image;
|
|
}
|
|
|
|
if (!m_diagram->views().isEmpty())
|
|
{
|
|
if (auto *editor = QETApp::diagramEditorAncestorOf(m_diagram->views().constFirst()))
|
|
editor->statusBar()->clearMessage();
|
|
}
|
|
|
|
foreach (QGraphicsView *view, m_diagram->views())
|
|
view->setContextMenuPolicy((Qt::DefaultContextMenu));
|
|
}
|
|
|
|
/**
|
|
@brief DiagramEventAddImage::showHint
|
|
Re-asserted on every move (see mouseMoveEvent), not just once at
|
|
activation: Qt's own built-in "show an action's statusTip on hover"
|
|
has its own internal "restore whatever was there before" logic for
|
|
when the hover ends. Since this message is first shown *during* that
|
|
same hover session (the user is still over the toolbar icon when the
|
|
deferred constructor-time call above fires), Qt's hover-tracking has
|
|
no idea this code changed the status bar in the meantime -- the
|
|
moment the mouse leaves the icon for the canvas, it silently
|
|
restores whatever it remembers being there before its own tip
|
|
started, overwriting this one. Re-showing it on every move within
|
|
the canvas simply outlasts that one-time restore -- the exact same
|
|
issue already found and fixed for the shape tools.
|
|
*/
|
|
void DiagramEventAddImage::showHint() const
|
|
{
|
|
if (m_diagram->views().isEmpty())
|
|
return;
|
|
if (auto *editor = QETApp::diagramEditorAncestorOf(m_diagram->views().constFirst()))
|
|
editor->statusBar()->showMessage(tr("Clic : positionner à la taille d'origine. "
|
|
"Cliquer-glisser : positionner et redimensionner. "
|
|
"Clic droit : pivoter de 90°. Ctrl+molette : ajuster la taille."));
|
|
}
|
|
|
|
/**
|
|
@brief DiagramEventAddImage::mousePressEvent
|
|
Left button: starts a potential drag-to-resize, anchored here -- but
|
|
doesn't commit to anything yet. A quick click-release (see
|
|
mouseMoveEvent's threshold check) still places the image at its
|
|
original size, matching the previous behavior exactly; only an
|
|
actual drag switches to resizing. Right button still rotates in 90
|
|
degree steps, unchanged, and only while not already left-dragging.
|
|
@param event : event of mouse pressed
|
|
*/
|
|
void DiagramEventAddImage::mousePressEvent(QGraphicsSceneMouseEvent *event)
|
|
{
|
|
if (m_image && event->button() == Qt::LeftButton)
|
|
{
|
|
m_pressed = true;
|
|
m_resize_engaged = false;
|
|
m_press_pos = event->scenePos();
|
|
event->setAccepted(true);
|
|
}
|
|
else if (m_image && !m_pressed && event->button() == Qt::RightButton)
|
|
{
|
|
// rotationAngle()/setRotationAngle(), not QGraphicsItem's own
|
|
// rotation()/setRotation(): DiagramImageItem's whole handle/undo/
|
|
// XML-save machinery reads exclusively from its own m_transform
|
|
// (see diagramimageitem.cpp's toXml() comment) and never looks at
|
|
// QGraphicsItem's built-in convenience property at all -- using
|
|
// it here left a rotation that displayed correctly in this tool
|
|
// but silently vanished the moment the item was saved and
|
|
// reloaded, and additionally desynced the rotate-handle's pivot
|
|
// math once the image was later selected for editing.
|
|
m_image->setRotationAngle(m_image->rotationAngle() + 90);
|
|
event->setAccepted(true);
|
|
}
|
|
}
|
|
|
|
/**
|
|
@brief DiagramEventAddImage::mouseMoveEvent
|
|
Action when mouse move
|
|
@param event : event of mouse move
|
|
*/
|
|
void DiagramEventAddImage::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
|
|
{
|
|
if (!m_image) {
|
|
return;
|
|
}
|
|
|
|
showHint();
|
|
|
|
QPointF pos = event->scenePos();
|
|
|
|
if (!m_is_added)
|
|
{
|
|
for (QGraphicsView *view : m_diagram->views()) {
|
|
view->setContextMenuPolicy((Qt::NoContextMenu));
|
|
}
|
|
|
|
m_diagram->addItem(m_image);
|
|
m_is_added = true;
|
|
}
|
|
|
|
if (m_pressed)
|
|
{
|
|
// Anchored on m_press_pos, not the item's own current position:
|
|
// dragging in any direction has to visibly grow the image from
|
|
// where the click started, not from wherever the "no button
|
|
// held" preview phase happened to leave it centered.
|
|
const QPointF delta = pos - m_press_pos;
|
|
if (!m_resize_engaged && QLineF(m_press_pos, pos).length() >= 4.0)
|
|
m_resize_engaged = true; // latched: crossing back within the threshold afterward must not un-engage it
|
|
|
|
if (!m_resize_engaged)
|
|
{
|
|
// Still just a (so far) plain click -- keep behaving like
|
|
// the pre-drag preview: original size, centered here, so
|
|
// releasing right now reproduces the old click-to-place
|
|
// behavior exactly.
|
|
m_image->setPos(m_press_pos - m_image->boundingRect().center());
|
|
}
|
|
else
|
|
{
|
|
// setPivot(QPointF(0, 0)), not left at its default
|
|
// boundingRect().center(): DiagramImageItem's own m_transform
|
|
// is pivot-centered, so with the default center pivot the
|
|
// setPos(qMin(...)) below would no longer land on the image's
|
|
// actual top-left corner the moment scale != 1 (the corner
|
|
// only coincides with pos() when the pivot is the local
|
|
// origin). Anchoring here to (0, 0) -- exactly the same
|
|
// temporary-anchor trick DiagramImageItem::handlerMousePressEvent()
|
|
// already uses for its own Resize handles -- makes pos()
|
|
// keep meaning "scene position of the top-left corner"
|
|
// regardless of scale, so the qMin(...) line below still
|
|
// needs no change at all. setPivot() itself is a no-op past
|
|
// the first call (same pivot value), and compensates pos()
|
|
// automatically so nothing visibly jumps at the switch.
|
|
m_image->setPivot(QPointF(0, 0));
|
|
|
|
const QSizeF naturalSize = m_image->boundingRect().size();
|
|
if (naturalSize.width() > 0 && naturalSize.height() > 0)
|
|
{
|
|
const qreal scaleX = qAbs(delta.x()) / naturalSize.width();
|
|
const qreal scaleY = qAbs(delta.y()) / naturalSize.height();
|
|
// The larger of the two, not a per-axis stretch: images
|
|
// only support a single uniform scale today (see
|
|
// boundingRect()/paint(), which never touch aspect
|
|
// ratio), so this is a diagonal-drag size, not a
|
|
// free-form one -- breaking aspect ratio on purpose is
|
|
// its own, separate, larger piece of work.
|
|
const qreal newScale = qBound(0.01, qMax(scaleX, scaleY), 50.0);
|
|
// scaleFactorX()/scaleFactorY(), not QGraphicsItem's own
|
|
// scale(): see the mousePressEvent right-click rotate
|
|
// comment above for why -- identical reasoning, identical
|
|
// fix.
|
|
m_image->setScaleFactorX(newScale);
|
|
m_image->setScaleFactorY(newScale);
|
|
}
|
|
m_image->setPos(qMin(m_press_pos.x(), pos.x()), qMin(m_press_pos.y(), pos.y()));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
m_image->setPos(pos - m_image->boundingRect().center());
|
|
}
|
|
|
|
event->setAccepted(true);
|
|
}
|
|
|
|
/**
|
|
@brief DiagramEventAddImage::mouseReleaseEvent
|
|
Left button release commits whatever mouseMoveEvent last set --
|
|
original size and centered if the press never turned into a real
|
|
drag, or the dragged-out size and position otherwise. Either way,
|
|
this is the only place placement is actually finalized now; a plain
|
|
click no longer finishes inside mousePressEvent itself, since it has
|
|
to wait and see whether a drag follows.
|
|
@param event : event of mouse release
|
|
*/
|
|
void DiagramEventAddImage::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
|
|
{
|
|
if (m_image && m_pressed && event->button() == Qt::LeftButton)
|
|
{
|
|
// Undo whatever temporary origin-anchoring mouseMoveEvent's
|
|
// resize-drag branch did (a no-op if it never engaged): every
|
|
// other DiagramImageItem code path -- handles, undo, XML save --
|
|
// expects an image's pivot to be its bounding-rect center unless
|
|
// the user deliberately customized it, exactly like a completed
|
|
// handle-resize already resets it via resetPivotToBoundingRectCenter().
|
|
// Doing this before reading pos() below is what makes the pushed
|
|
// command capture the final, center-pivot position rather than
|
|
// the drag's temporary corner-anchored one.
|
|
m_image->setPivot(m_image->boundingRect().center());
|
|
|
|
m_diagram->undoStack().push(new AddGraphicsObjectCommand(m_image, m_diagram, m_image->pos()));
|
|
|
|
for (QGraphicsView *view : m_diagram->views()) {
|
|
view->setContextMenuPolicy((Qt::DefaultContextMenu));
|
|
}
|
|
|
|
m_running = false;
|
|
emit finish();
|
|
event->setAccepted(true);
|
|
}
|
|
}
|
|
|
|
/**
|
|
@brief DiagramEventAddImage::mouseDoubleClickEvent
|
|
This method is used only to overwrite double click.
|
|
When double click, image properties dialog isn't open.
|
|
@param event : event of mouse double click.
|
|
*/
|
|
void DiagramEventAddImage::mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event) {
|
|
event->setAccepted(true);
|
|
}
|
|
|
|
/**
|
|
@brief DiagramEventAddImage::wheelEvent
|
|
Action when mouse wheel is rotated
|
|
@param event: evet of mouse wheel
|
|
*/
|
|
void DiagramEventAddImage::wheelEvent(QGraphicsSceneWheelEvent *event)
|
|
{
|
|
// !m_pressed added alongside the modifier fix: without it, wheel
|
|
// scaling could fight with an active drag-resize, both trying to
|
|
// set scale() from different sources in the same gesture.
|
|
// event->modifiers() & Qt::ControlModifier, not != Qt::CTRL: the
|
|
// same exact-equality bug already found and fixed several times
|
|
// this session elsewhere -- Ctrl held together with any other
|
|
// modifier would silently fail to register as Ctrl at all.
|
|
if (!m_is_added || !m_image || m_pressed || !(event->modifiers() & Qt::ControlModifier)) {
|
|
return;
|
|
}
|
|
|
|
// scaleFactorX()/scaleFactorY(), not QGraphicsItem's own scale(): see
|
|
// the right-click rotate comment in mousePressEvent for why. Wheel-
|
|
// scaling only ever runs while !m_pressed (guarded above), i.e. before
|
|
// any drag-resize has anchored the pivot to the origin (see
|
|
// mouseMoveEvent), so the pivot here is still the default
|
|
// boundingRect().center() and this scales the image in place around
|
|
// its own middle, exactly like before.
|
|
//
|
|
// Step each axis from its own current value rather than reading X and
|
|
// writing it back to both: scaleFactorX and scaleFactorY cannot
|
|
// actually differ at this point today (every other mutator in this
|
|
// class -- the drag-resize branch above, and this same wheelEvent --
|
|
// only ever sets them to the same value, and mouseReleaseEvent commits
|
|
// and ends this tool on any left-button release, so a handle-based
|
|
// non-uniform resize can never happen first and leave this instance
|
|
// still alive). Stepping both from their own value rather than
|
|
// collapsing Y to X costs nothing today and removes the trap if that
|
|
// invariant ever stops holding.
|
|
qreal scalingX = m_image->scaleFactorX();
|
|
qreal scalingY = m_image->scaleFactorY();
|
|
const qreal step = event->delta() > 1 ? 0.01 : -0.01;
|
|
scalingX += step;
|
|
scalingY += step;
|
|
if (scalingX > 0.01 && scalingX <= 2 && scalingY > 0.01 && scalingY <= 2) {
|
|
m_image->setScaleFactorX(scalingX);
|
|
m_image->setScaleFactorY(scalingY);
|
|
}
|
|
|
|
event->setAccepted(true);
|
|
}
|
|
|
|
/**
|
|
@brief DiagramEventAddImage::isNull
|
|
@return : true if image can't be loaded, otherwise return false.
|
|
*/
|
|
bool DiagramEventAddImage::isNull() const
|
|
{
|
|
if (!m_image) return true;
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
@brief DiagramEventAddImage::openDialog
|
|
Open dialog to select the image to add.
|
|
*/
|
|
void DiagramEventAddImage::openDialog()
|
|
{
|
|
if (m_diagram -> isReadOnly()) return;
|
|
|
|
//Open dialog to select image
|
|
QString pathPictures = QETApp::pictureDir();
|
|
QString fileName = QFileDialog::getOpenFileName(m_diagram->views().isEmpty()? nullptr : m_diagram->views().first(), QObject::tr("Selectionner une image..."), pathPictures, QObject::tr("Image Files (*.png *.jpg *.jpeg *.bmp *.svg)"));
|
|
|
|
if (fileName.isEmpty()) return;
|
|
|
|
QImage image(fileName);
|
|
if(image.isNull())
|
|
{
|
|
QMessageBox::critical(m_diagram->views().isEmpty()? nullptr : m_diagram->views().first(), QObject::tr("Erreur"), QObject::tr("Impossible de charger l'image."));
|
|
return;
|
|
}
|
|
|
|
m_image = new DiagramImageItem (QPixmap::fromImage(image));
|
|
m_running = true;
|
|
}
|