Add "Scale element..." to the element editor, keeping terminals on the grid

Edit > Scale element... scales the whole element about its hotspot by a
factor picked from x0.5, x1.5, x2, x2.5, x3, x4. Only the factors that
leave every terminal on the 10 px folio grid are offered, so wires to a
scaled element stay straight. An element whose terminals are off the grid
today is offered only the factors that bring them on to it; if none does,
the dialog says so and OK is disabled.

Drawn parts are scaled with their existing handleUserTransformation(), as
the resize handles do. Texts, dynamic text fields and terminals are set
directly so that font sizes (whole points, 4 pt minimum), terminal name
offsets and line end sizes scale too, which the resize handles do not do.
One undo step restores the element.

Unlike "Import an element to resize", this needs no external program and
works on the element being edited.

Grew out of issue #1338.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
ispyisail
2026-10-08 07:11:58 +13:00
parent 06083c455d
commit 35162b6731
10 changed files with 502 additions and 1 deletions
+95
View File
@@ -17,6 +17,7 @@
*/
#include "editorcommands.h"
#include "../diagram.h"
#include "symbolscale.h"
/**
@brief ElementEditionCommand::ElementEditionCommand
@@ -785,3 +786,97 @@ void FlipElementsCommand::undo()
{
redo();
}
/**
@brief ScaleElementCommand::ScaleElementCommand
@param scene : the element to scale
@param factor : scale factor, applied about the hotspot
@param scale_text : also scale font sizes
@param parent : parent undo command
*/
ScaleElementCommand::ScaleElementCommand(ElementScene *scene,
qreal factor,
bool scale_text,
QUndoCommand *parent) :
ElementEditionCommand(QObject::tr("mise à l'échelle de l'élément", "undo caption"),
scene, nullptr, parent),
m_factor(factor)
{
const auto scaledFont = [factor, scale_text](QFont font) {
if (scale_text) {
if (font.pointSizeF() > 0) {
font.setPointSize(SymbolScale::scaledFontSize(font.pointSizeF(), factor));
} else if (font.pixelSize() > 0) {
font.setPixelSize(SymbolScale::scaledFontSize(font.pixelSize(), factor));
}
}
return font;
};
const auto parts = scene->primitives();
for (CustomElementPart *part : parts)
{
QGraphicsItem *item = part->toItem();
//Font before position: changing the font can move a text
//to keep its alignment.
if (auto text = qgraphicsitem_cast<PartText *>(item)) {
addChange(text, "font", scaledFont(text->font()));
addChange(text, "pos", text->pos() * factor);
}
else if (auto field = qgraphicsitem_cast<PartDynamicTextField *>(item)) {
addChange(field, "font", scaledFont(field->font()));
if (field->textWidth() > 0) {
addChange(field, "textWidth", field->textWidth() * factor);
}
addChange(field, "pos", field->pos() * factor);
}
else if (auto terminal = qgraphicsitem_cast<PartTerminal *>(item)) {
addChange(terminal, "label_font", scaledFont(terminal->labelFont()));
addChange(terminal, "label_pos", terminal->labelPos() * factor);
addChange(terminal, "pos", terminal->pos() * factor);
}
else {
m_geometry_parts << part;
if (auto line = qgraphicsitem_cast<PartLine *>(item)) {
addChange(line, "length1", line->firstEndLength() * factor);
addChange(line, "length2", line->secondEndLength() * factor);
}
}
}
}
void ScaleElementCommand::addChange(QObject *object, const char *name, const QVariant &after)
{
m_changes << PropertyChange{object, name, object->property(name), after};
}
/**
@brief ScaleElementCommand::scaleGeometry
Scale the drawn parts about the hotspot, from \a from times their
original size to \a to times it.
*/
void ScaleElementCommand::scaleGeometry(qreal from, qreal to)
{
const QRectF before(0, 0, from, from);
const QRectF after(0, 0, to, to);
for (CustomElementPart *part : std::as_const(m_geometry_parts)) {
part->startUserTransformation(before);
part->handleUserTransformation(before, after);
}
}
void ScaleElementCommand::undo()
{
scaleGeometry(m_factor, 1);
for (const PropertyChange &change : std::as_const(m_changes)) {
change.object->setProperty(change.name.constData(), change.before);
}
}
void ScaleElementCommand::redo()
{
scaleGeometry(1, m_factor);
for (const PropertyChange &change : std::as_const(m_changes)) {
change.object->setProperty(change.name.constData(), change.after);
}
}