mirror of
https://github.com/qelectrotech/qelectrotech-source-mirror.git
synced 2026-09-30 14:54:13 +02:00
Add editable resize/rotate/skew handles for shapes and images
Shapes and images can now be resized, rotated, and skewed directly on the canvas, not just moved. Both share one small transform struct (rotation, then skew, then scale, anchored on a movable pivot) and one handle widget, so a corner drag, an edge skew, or grabbing the rotate handle behaves the same way and runs through the same matrix math everywhere, instead of every item type reinventing its own. Shapes also gained a proper pen tool (bezier paths, corner/smooth/ symmetric nodes), arc support, and mirroring. Images gained non-destructive cropping and colour-keyed transparency, both remember their own settings, so reopening the dialog picks up where you left off instead of starting over. Properties dialogs for both were extended to match (position, size, angle, skew), with undo/redo wired through for every handle drag. Old XML files can read easily as the transformation is only added if needed and the old syntax is still used and understood if it is not needed.
This commit is contained in:
@@ -19,10 +19,14 @@
|
||||
#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
|
||||
@@ -34,6 +38,16 @@ DiagramEventAddImage::DiagramEventAddImage(Diagram *diagram) :
|
||||
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(); });
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -47,33 +61,61 @@ DiagramEventAddImage::~DiagramEventAddImage()
|
||||
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
|
||||
Action when mouse is pressed
|
||||
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)
|
||||
if (m_image && event->button() == Qt::LeftButton)
|
||||
{
|
||||
QPointF pos = event->scenePos();
|
||||
pos.rx() -= m_image->boundingRect().width()/2;
|
||||
pos.ry() -= m_image->boundingRect().height()/2;
|
||||
m_diagram -> undoStack().push (new AddGraphicsObjectCommand(m_image, m_diagram, pos));
|
||||
|
||||
for (QGraphicsView *view : m_diagram->views()) {
|
||||
view->setContextMenuPolicy((Qt::DefaultContextMenu));
|
||||
}
|
||||
|
||||
m_running = false;
|
||||
emit finish();
|
||||
m_pressed = true;
|
||||
m_resize_engaged = false;
|
||||
m_press_pos = event->scenePos();
|
||||
event->setAccepted(true);
|
||||
}
|
||||
else if (m_image && event -> button() == Qt::RightButton)
|
||||
else if (m_image && !m_pressed && event->button() == Qt::RightButton)
|
||||
{
|
||||
m_image->setRotation(m_image->rotation() + 90);
|
||||
event->setAccepted(true);
|
||||
@@ -87,26 +129,95 @@ void DiagramEventAddImage::mousePressEvent(QGraphicsSceneMouseEvent *event)
|
||||
*/
|
||||
void DiagramEventAddImage::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
|
||||
{
|
||||
if (!m_image || event->buttons() != Qt::NoButton) {
|
||||
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;
|
||||
}
|
||||
|
||||
m_image->setPos(pos - m_image->boundingRect().center());
|
||||
|
||||
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
|
||||
{
|
||||
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);
|
||||
m_image->setScale(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)
|
||||
{
|
||||
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.
|
||||
@@ -124,7 +235,14 @@ void DiagramEventAddImage::mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event
|
||||
*/
|
||||
void DiagramEventAddImage::wheelEvent(QGraphicsSceneWheelEvent *event)
|
||||
{
|
||||
if (!m_is_added || !m_image || event -> modifiers() != Qt::CTRL) {
|
||||
// !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;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user