/*
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 .
*/
#include "textresizehandles.h"
#include "qetgraphicshandleritem.h"
#include
#include
#include
#include
#include
#include
/**
@brief TextResizeHandles::TextResizeHandles
@param text : the text to resize, becomes the parent of the handles.
It must be in a scene, the handles filter their own events.
@param handle_size : see QETUtils::graphicsHandlerSize()
*/
TextResizeHandles::TextResizeHandles(QGraphicsTextItem *text, qreal handle_size) :
m_text(text)
{
setFlag(QGraphicsItem::ItemHasNoContents);
setParentItem(text);
for (int i = TextResize::TopLeft ; i <= TextResize::BottomLeft ; ++i)
{
auto *handle = new QetGraphicsHandlerItem(handle_size);
handle->setParentItem(this);
handle->setColor(Qt::darkGreen);
handle->setToolTip(tr("Glisser pour changer la largeur du texte, "
"double-cliquer pour une largeur automatique"));
handle->setCursor(i == TextResize::TopLeft || i == TextResize::BottomRight
? Qt::SizeFDiagCursor : Qt::SizeBDiagCursor);
if (scene())
handle->installSceneEventFilter(this);
m_handles << handle;
}
m_size_connection = connect(text->document()->documentLayout(),
&QAbstractTextDocumentLayout::documentSizeChanged,
this, &TextResizeHandles::updateHandlesPos);
updateHandlesPos();
}
TextResizeHandles::~TextResizeHandles()
{
disconnect(m_size_connection);
}
QRectF TextResizeHandles::boundingRect() const {
return QRectF();
}
/**
@return the handle at corner
*/
QetGraphicsHandlerItem *TextResizeHandles::handle(TextResize::Corner corner) const {
return m_handles.at(corner);
}
/**
@brief TextResizeHandles::updateHandlesPos
Put the handles at the corners of the box of the text. Called when the
size of the text changes; the owner calls it when the box changes in an
other way.
The box is boundingRect(), the box a user sees selected and resizes,
not the tighter frame of a framed text (qelectrotech#591).
*/
void TextResizeHandles::updateHandlesPos()
{
const QRectF rect = m_text->boundingRect();
for (int i = TextResize::TopLeft ; i <= TextResize::BottomLeft ; ++i)
m_handles.at(i)->setPos(TextResize::cornerOf(rect, TextResize::Corner(i)));
}
bool TextResizeHandles::sceneEventFilter(QGraphicsItem *watched, QEvent *event)
{
const int index = m_handles.indexOf(static_cast(watched));
if (index < 0)
return false;
switch (event->type())
{
case QEvent::GraphicsSceneMousePress:
{
auto *mouse_event = static_cast(event);
if (mouse_event->button() != Qt::LeftButton)
return false;
pressed(TextResize::Corner(index), mouse_event->scenePos());
return true;
}
case QEvent::GraphicsSceneMouseMove:
if (m_dragging)
moved(static_cast(event)->scenePos());
return true;
case QEvent::GraphicsSceneMouseRelease:
released();
return true;
case QEvent::GraphicsSceneMouseDoubleClick:
resetToAutomaticWidth();
return true;
default:
return false;
}
}
void TextResizeHandles::pressed(TextResize::Corner corner, const QPointF &scene_pos)
{
m_dragging = true;
m_press_scene = scene_pos;
m_dragged = corner;
m_old_width = currentWidth();
m_old_pos = m_text->pos();
m_press_inverse = m_text->sceneTransform().inverted();
m_fixed_local = TextResize::cornerOf(m_text->boundingRect(), TextResize::opposite(corner));
m_fixed_scene = m_text->mapToScene(m_fixed_local);
m_min_width = TextResize::minimumWidth(m_text->document());
}
void TextResizeHandles::moved(const QPointF &scene_pos)
{
//A click without a move keeps an automatic width automatic
if (scene_pos == m_press_scene)
return;
//Whole pixels, as in the width spin boxes
const qreal width = qMax(qreal(qRound(TextResize::widthForDrag(m_press_inverse, m_fixed_local,
scene_pos, m_dragged, 0))),
std::ceil(m_min_width));
m_text->setProperty("textWidth", width);
TextResize::pinCorner(m_text, TextResize::opposite(m_dragged), m_fixed_scene);
updateHandlesPos();
}
void TextResizeHandles::released()
{
if (!m_dragging)
return;
m_dragging = false;
const qreal new_width = currentWidth();
if (!qFuzzyCompare(m_old_width, new_width) || m_old_pos != m_text->pos())
emit resizeFinished(m_old_width, new_width, m_old_pos, m_text->pos());
}
/**
@brief TextResizeHandles::resetToAutomaticWidth
Back to the automatic width (-1): the text keeps the point chosen by its
alignment in place, as it does for any other change of its width.
*/
void TextResizeHandles::resetToAutomaticWidth()
{
m_dragging = false;
const qreal old_width = currentWidth();
if (old_width < 0)
return;
const QPointF old_pos = m_text->pos();
m_text->setProperty("textWidth", qreal(-1));
updateHandlesPos();
emit resizeFinished(old_width, currentWidth(), old_pos, m_text->pos());
}
qreal TextResizeHandles::currentWidth() const {
return m_text->property("textWidth").toReal();
}
/**
@brief TextResizeCommand::TextResizeCommand
@param text : a text with a "textWidth" property
@param old_width
@param new_width
@param old_pos
@param new_pos
@param parent
*/
TextResizeCommand::TextResizeCommand(QGraphicsObject *text,
qreal old_width, qreal new_width,
const QPointF &old_pos, const QPointF &new_pos,
QUndoCommand *parent) :
QUndoCommand(parent),
m_text(text),
m_old_width(old_width),
m_new_width(new_width),
m_old_pos(old_pos),
m_new_pos(new_pos)
{
setText(tr("Redimensionner un texte"));
}
void TextResizeCommand::undo() {
apply(m_old_width, m_old_pos);
}
void TextResizeCommand::redo() {
apply(m_new_width, m_new_pos);
}
void TextResizeCommand::apply(qreal width, const QPointF &pos)
{
if (!m_text)
return;
m_text->setProperty("textWidth", width);
m_text->setPos(pos);
}