Files
qelectrotech-source-mirror/sources/editor/graphicspart/partdynamictextfield.cpp
T
Dieter Mayer 68ffacc9e6 Salvage font descriptions written by Qt 6.11+ when parsing fails
QFont::fromString() of Qt 5.x and Qt <= 6.10 rejects the >= 19 field
descriptions QFont::toString() emits since Qt 6.11, silently leaving a
broken font at every read site. Add QETUtils::fontFromString(): try the
native parser first, and on failure re-compose the legacy 10/11 field
form from the known Qt 6.11 field layout (OpenType weight mapped back
to the legacy scale) so no font information stored in existing files is
lost. Also salvage the 21 field double-serialized descriptions left
behind by some historical builds (a complete legacy description
embedded as the family name of a second one) by taking the embedded
leading description, matching what the lenient parser of Qt 6.11+
resolves them to. All font read sites now go through the helper; on
failure the default font of the caller is left untouched instead of a
cleared family.

Verified end to end on a Qt 5.15 build: a project whose 53 font
attributes were rewritten into the 19 field Qt 6.11 format loads and
autosaves byte-identical to the original legacy file (family, sizes,
bold/italic/underline, style name all preserved), and a mixed file
containing the exact 21 field string from the issue comes back
normalized as "Caladea,9,-1,5,75,1,0,0,0,0,Bold Italic".

See issue #553.
2026-07-31 08:10:34 +02:00

666 lines
20 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 "partdynamictextfield.h"
#include "../../QPropertyUndoCommand/qpropertyundocommand.h"
#include "../../qetapp.h"
#include "../elementscene.h"
#include "../../utils/qetutils.h"
#include <QApplication>
#include <QColor>
#include <QFont>
#include <QGraphicsSceneMouseEvent>
PartDynamicTextField::PartDynamicTextField(QETElementEditor *editor, QGraphicsItem *parent) :
QGraphicsTextItem(parent),
CustomElementPart(editor),
m_uuid(QUuid::createUuid())
{
setDefaultTextColor(Qt::black);
setFont(QETApp::dynamicTextsItemFont());
QSettings settings;
QGraphicsObject::setRotation(QET::correctAngle(settings.value("diagrameditor/dynamic_text_rotation", 0).toInt()));
setTextWidth(settings.value("diagrameditor/dynamic_text_width", -1).toInt());
setText("_");
setTextFrom(DynamicElementTextItem::UserText);
setFlags(
QGraphicsItem::ItemIsSelectable |
QGraphicsItem::ItemSendsGeometryChanges |
QGraphicsItem::ItemIsMovable
);
//Option when text is displayed in multiple line
QTextOption option = document() -> defaultTextOption();
option.setAlignment(Qt::AlignHCenter);
option.setWrapMode(QTextOption::WordWrap);
document() -> setDefaultTextOption(option);
}
QString PartDynamicTextField::name() const
{
return tr("Champ de texte dynamique", "element part name");
}
QString PartDynamicTextField::xmlName() const
{
return QString("dynamic_text");
}
/**
Redefines setRotation
@param angle
*/
void PartDynamicTextField::setRotation(qreal angle) {
qreal diffAngle = qRound((angle - rotation()) * 100.0) / 100.0;
QGraphicsObject::setRotation(QET::correctAngle(angle, true));
setPos(QTransform().rotate(diffAngle).map(pos()));
}
void PartDynamicTextField::mirror() {
// at first: rotate the text:
QGraphicsObject::setRotation(QET::correctAngle(360-rotation(), true));
// then see, where we need to re-position depending on the angle!
qreal rot = qRound(QET::correctAngle(rotation(), true));
qreal c = qCos(qDegreesToRadians(rot));
qreal s = qSin(qDegreesToRadians(rot));
qreal x = (-1) * pos().x() - c * boundingRect().width();
qreal y = pos().y() - s * boundingRect().width();
setPos(x, y);
}
void PartDynamicTextField::flip() {
// at first: rotate the text:
QGraphicsObject::setRotation(QET::correctAngle(360-rotation(), true));
// then see, where we need to re-position depending on the angle!
qreal rot = qRound(QET::correctAngle(rotation(), true));
qreal c = qCos(qDegreesToRadians(rot));
qreal s = qSin(qDegreesToRadians(rot));
qreal x = pos().x() + s * boundingRect().height();
qreal y = (-1) * pos().y() - c * boundingRect().height();
setPos(x, y);
}
/**
@brief PartDynamicTextField::startUserTransformation
@param initial_selection_rect
Start the user-induced transformation,
provided this primitive is contained
within the initial_selection_rect bounding rectangle.
*/
void PartDynamicTextField::startUserTransformation(const QRectF &initial_selection_rect) {
Q_UNUSED(initial_selection_rect)
m_saved_point = pos(); // scene coordinates, no need to mapFromScene()
}
/**
@brief PartDynamicTextField::handleUserTransformation
@param initial_selection_rect
@param new_selection_rect
Handle the user-induced transformation
from initial_selection_rect to new_selection_rect
*/
void PartDynamicTextField::handleUserTransformation(
const QRectF &initial_selection_rect,
const QRectF &new_selection_rect)
{
QPointF new_pos = mapPoints(
initial_selection_rect, new_selection_rect, QList<QPointF>() << m_saved_point).first();
setPos(new_pos);
}
/**
@brief PartDynamicTextField::toXml
@param dom_doc
@return
*/
const QDomElement PartDynamicTextField::toXml(QDomDocument &dom_doc) const
{
QDomElement root_element = dom_doc.createElement(xmlName());
qreal x = (qRound(pos().x() * 100.0) / 100.0);
qreal y = (qRound(pos().y() * 100.0) / 100.0);
qreal rot = (qRound(rotation() * 10.0) / 10.0);
root_element.setAttribute("x", QString::number(x));
root_element.setAttribute("y", QString::number(y));
root_element.setAttribute("z", QString::number(zValue()));
root_element.setAttribute("rotation", QString::number(QET::correctAngle(rot)));
root_element.setAttribute("font", QETUtils::fontToString(font()));
root_element.setAttribute("uuid", m_uuid.toString());
root_element.setAttribute("frame", m_frame? "true" : "false");
root_element.setAttribute("text_width", QString::number(m_text_width));
root_element.setAttribute("keep_visual_rotation", m_keep_visual_rotation ? "true" : "false");
QMetaEnum me = DynamicElementTextItem::textFromMetaEnum();
root_element.setAttribute("text_from", me.valueToKey(m_text_from));
me = QMetaEnum::fromType<Qt::Alignment>();
if(this -> alignment() &Qt::AlignRight)
root_element.setAttribute("Halignment", me.valueToKey(Qt::AlignRight));
else if(this -> alignment() &Qt::AlignLeft)
root_element.setAttribute("Halignment", me.valueToKey(Qt::AlignLeft));
else if(this -> alignment() &Qt::AlignHCenter)
root_element.setAttribute("Halignment", me.valueToKey(Qt::AlignHCenter));
if(this -> alignment() &Qt::AlignBottom)
root_element.setAttribute("Valignment", me.valueToKey(Qt::AlignBottom));
else if(this -> alignment() & Qt::AlignTop)
root_element.setAttribute("Valignment", me.valueToKey(Qt::AlignTop));
else if(this -> alignment() &Qt::AlignVCenter)
root_element.setAttribute("Valignment", me.valueToKey(Qt::AlignVCenter));
QDomElement dom_text = dom_doc.createElement("text");
dom_text.appendChild(dom_doc.createTextNode(toPlainText()));
root_element.appendChild(dom_text);
//Info name
if(!m_info_name.isEmpty()) {
QDomElement dom_info_name = dom_doc.createElement("info_name");
dom_info_name.appendChild(dom_doc.createTextNode(m_info_name));
root_element.appendChild(dom_info_name);
}
//Composite text
if(!m_composite_text.isEmpty()) {
QDomElement dom_comp_text = dom_doc.createElement("composite_text");
dom_comp_text.appendChild(dom_doc.createTextNode(m_composite_text));
root_element.appendChild(dom_comp_text);
}
//Color
if(color() != QColor(Qt::black)) {
QDomElement dom_color = dom_doc.createElement("color");
dom_color.appendChild(dom_doc.createTextNode(color().name()));
root_element.appendChild(dom_color);
}
return root_element;
}
/**
@brief PartDynamicTextField::fromXml
@param dom_elmt
*/
void PartDynamicTextField::fromXml(const QDomElement &dom_elmt) {
if (dom_elmt.tagName() != xmlName()) {
qDebug() << "PartDynamicTextField::fromXml : Wrong tagg name";
return;
}
QGraphicsTextItem::setPos(
dom_elmt.attribute("x", QString::number(0)).toDouble(),
dom_elmt.attribute("y", QString::number(0)).toDouble()
);
setZValue(dom_elmt.attribute("z", QString::number(zValue())).toDouble());
QGraphicsObject::setRotation(QET::correctAngle(dom_elmt.attribute("rotation", QString::number(0)).toDouble()));
setKeepVisualRotation(dom_elmt.attribute("keep_visual_rotation", "true") == "true"? true : false);
if (dom_elmt.hasAttribute("font")) {
QFont font_;
QETUtils::fontFromString(font_, dom_elmt.attribute("font"));
setFont(font_);
}
else if (dom_elmt.hasAttribute("font_size")) {
// to support font-size from old 'input'
setFont(QETApp::dynamicTextsItemFont(dom_elmt.attribute("font_size", QString::number(9)).toInt()));
}
else {
#if TODO_LIST
#pragma message("@TODO remove in futur")
#endif
//Keep compatibility TODO remove in futur
setFont(QETApp::dynamicTextsItemFont(9));
}
m_uuid = QUuid(dom_elmt.attribute("uuid", QUuid::createUuid().toString()));
setFrame(dom_elmt.attribute("frame", "false") == "true"? true : false);
setTextWidth(dom_elmt.attribute("text_width", QString::number(-1)).toDouble());
QMetaEnum me = DynamicElementTextItem::textFromMetaEnum();
m_text_from = DynamicElementTextItem::TextFrom(
me.keyToValue(dom_elmt.attribute("text_from").toStdString().data()));
me = QMetaEnum::fromType<Qt::Alignment>();
if(dom_elmt.hasAttribute("Halignment"))
setAlignment(Qt::Alignment(
me.keyToValue(dom_elmt.attribute("Halignment").toStdString().data())));
if(dom_elmt.hasAttribute(("Valignment")))
setAlignment(Qt::Alignment(
me.keyToValue(dom_elmt.attribute("Valignment").toStdString().data())) | this -> alignment());
//Text
QDomElement dom_text = dom_elmt.firstChildElement("text");
if (!dom_text.isNull()) {
m_text = dom_text.text();
m_block_alignment = true;
setPlainText(m_text);
m_block_alignment = false;
}
//Info name
QDomElement dom_info_name = dom_elmt.firstChildElement("info_name");
if(!dom_info_name.isNull())
m_info_name = dom_info_name.text();
//Composite text
QDomElement dom_comp_text = dom_elmt.firstChildElement("composite_text");
if(!dom_comp_text.isNull())
m_composite_text = dom_comp_text.text();
//Color
QDomElement dom_color = dom_elmt.firstChildElement("color");
if(!dom_color.isNull())
setColor(QColor(dom_color.text()));
}
/**
@brief PartDynamicTextField::fromTextFieldXml
Setup this text from the xml definition
of a text field (The xml tagg of a text field is "input");
@param dom_element
*/
void PartDynamicTextField::fromTextFieldXml(const QDomElement &dom_element)
{
if(dom_element.tagName() != "input")
return;
setFont(QETApp::diagramTextsFont(dom_element.attribute("size", QString::number(9)).toInt()));
if(dom_element.attribute("tagg", "none") == "none") {
setTextFrom(DynamicElementTextItem::UserText);
setText(dom_element.attribute("text", "_"));
}
else {
setTextFrom(DynamicElementTextItem::ElementInfo);
setInfoName(dom_element.attribute("tagg", "label"));
}
QGraphicsObject::setRotation(QET::correctAngle(dom_element.attribute("rotation", "0").toDouble()));
//the origin transformation point of PartDynamicTextField is the top left corner, no matter the font size
//The origin transformation point of PartTextField is the middle of left edge, and so by definition, change with the size of the font
//We need to use a QTransform to find the pos of this text from the saved pos of text item
QTransform transform;
//First make the rotation
transform.rotate(dom_element.attribute("rotation", "0").toDouble());
QPointF pos = transform.map(QPointF(0, -boundingRect().height()/2));
transform.reset();
//Second translate to the pos
transform.translate(
dom_element.attribute("x", QString::number(0)).toDouble(),
dom_element.attribute("y", QString::number(0)).toDouble()
);
QGraphicsTextItem::setPos(transform.map(pos));
}
/**
@brief PartDynamicTextField::textFrom
@return what the final text is created from.
*/
DynamicElementTextItem::TextFrom PartDynamicTextField::textFrom() const
{
return m_text_from;
}
/**
@brief PartDynamicTextField::setTextFrom
Set the final text is created from.
@param text_from
*/
void PartDynamicTextField::setTextFrom(DynamicElementTextItem::TextFrom text_from) {
m_text_from = text_from;
switch (m_text_from) {
case DynamicElementTextItem::UserText:
setPlainText(m_text);
break;
case DynamicElementTextItem::ElementInfo:
setInfoName(m_info_name);
break;
case DynamicElementTextItem::CompositeText:
setCompositeText(m_composite_text);
break;
default:
break;
}
emit textFromChanged(m_text_from);
}
/**
@brief PartDynamicTextField::text
@return the text of this text
*/
QString PartDynamicTextField::text() const
{
return m_text;
}
/**
@brief PartDynamicTextField::setText
Set the text of this text
@param text
*/
void PartDynamicTextField::setText(const QString &text) {
m_text = text;
setPlainText(m_text);
emit textChanged(m_text);
}
void PartDynamicTextField::setInfoName(const QString &info_name) {
m_info_name = info_name;
if(m_text_from == DynamicElementTextItem::ElementInfo && elementScene())
setPlainText(elementScene()->elementData().m_informations.value(m_info_name).toString());
emit infoNameChanged(m_info_name);
}
/**
@brief PartDynamicTextField::infoName
@return the info name of this text
*/
QString PartDynamicTextField::infoName() const{
return m_info_name;
}
/**
@brief PartDynamicTextField::setCompositeText
Set the composite text of this text item to text
@param text
*/
void PartDynamicTextField::setCompositeText(const QString &text) {
m_composite_text = text;
if(m_text_from == DynamicElementTextItem::CompositeText && elementScene())
setPlainText(autonum::AssignVariables::replaceVariable(m_composite_text, elementScene()->elementData().m_informations));
emit compositeTextChanged(m_composite_text);
}
/**
@brief PartDynamicTextField::compositeText
@return the composite text of this text
*/
QString PartDynamicTextField::compositeText() const
{
return m_composite_text;
}
/**
@brief PartDynamicTextField::setColor
@param color set text color to color
*/
void PartDynamicTextField::setColor(const QColor& color) {
setDefaultTextColor(color);
emit colorChanged(color);
}
/**
@brief PartDynamicTextField::color
@return The color of this text
*/
QColor PartDynamicTextField::color() const
{
return defaultTextColor();
}
void PartDynamicTextField::setFrame(bool frame) {
m_frame = frame;
update();
emit frameChanged(m_frame);
}
bool PartDynamicTextField::frame() const
{
return m_frame;
}
void PartDynamicTextField::setTextWidth(qreal width) {
this -> document() -> setTextWidth(width);
//Adjust the width, to ideal width if needed
if(width > 0 && document() -> size().width() > width)
document() -> setTextWidth(document() -> idealWidth());
m_text_width = document() -> textWidth();
emit textWidthChanged(m_text_width);
}
void PartDynamicTextField::setPlainText(const QString &text) {
if(toPlainText() == text)
return;
prepareAlignment();
QGraphicsTextItem::setPlainText(text);
//User define a text width
if(m_text_width > 0) {
if(document() -> size().width() > m_text_width) {
document() -> setTextWidth(m_text_width);
if(document() -> size().width() > m_text_width) {
document() -> setTextWidth(document() -> idealWidth());
}
}
}
finishAlignment();
}
void PartDynamicTextField::setAlignment(Qt::Alignment alignment) {
m_alignment = alignment;
emit alignmentChanged(m_alignment);
}
Qt::Alignment PartDynamicTextField::alignment() const
{
return m_alignment;
}
void PartDynamicTextField::setFont(const QFont &font) {
if (font == this -> font()) {
return;
}
prepareAlignment();
QGraphicsTextItem::setFont(font);
finishAlignment();
emit fontChanged(font);
}
void PartDynamicTextField::setKeepVisualRotation(const bool &keep)
{
if (keep == this->m_keep_visual_rotation) {
return;
}
m_keep_visual_rotation = keep;
emit keepVisualRotationChanged(keep);
}
bool PartDynamicTextField::keepVisualRotation() const {
return m_keep_visual_rotation;
}
/**
@brief PartDynamicTextField::mouseMoveEvent
@param event
*/
void PartDynamicTextField::mouseMoveEvent(QGraphicsSceneMouseEvent *event) {
if ((event->buttons() & Qt::LeftButton) && (flags() & QGraphicsItem::ItemIsMovable)) {
// Suppress spurious moves from the properties dock resizing the viewport.
const QPointF d = event->screenPos() - event->buttonDownScreenPos(Qt::LeftButton);
if (d.manhattanLength() < QApplication::startDragDistance())
return;
QPointF pos = event->scenePos() + (m_origin_pos - event->buttonDownScenePos(Qt::LeftButton));
event->modifiers() == Qt::ControlModifier ? setPos(pos) : setPos(elementScene()->snapToGrid(pos));
} else {
QGraphicsObject::mouseMoveEvent(event);
}
}
/**
@brief PartDynamicTextField::mousePressEvent
@param event
*/
void PartDynamicTextField::mousePressEvent(QGraphicsSceneMouseEvent *event) {
if(event -> button() == Qt::LeftButton)
m_origin_pos = this -> pos();
QGraphicsObject::mousePressEvent(event);
}
/**
@brief PartDynamicTextField::mouseReleaseEvent
@param event
*/
void PartDynamicTextField::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) {
if((event -> button() & Qt::LeftButton) &&
(flags() & QGraphicsItem::ItemIsMovable) &&
m_origin_pos != pos()) {
QPropertyUndoCommand *undo =\
new QPropertyUndoCommand(this, "pos", QVariant(m_origin_pos), QVariant(pos()));
undo -> setText(tr("Déplacer un champ texte"));
undo -> enableAnimation();
elementScene() -> undoStack().push(undo);
}
QGraphicsObject::mouseReleaseEvent(event);
}
/**
@brief PartDynamicTextField::itemChange
@param change
@param value
@return
*/
QVariant PartDynamicTextField::itemChange(QGraphicsItem::GraphicsItemChange change, const QVariant &value) {
if (change == QGraphicsItem::ItemPositionHasChanged || change == QGraphicsItem::ItemSceneHasChanged) {
updateCurrentPartEditor();
if(change == QGraphicsItem::ItemSceneHasChanged &&
m_first_add &&
elementScene() != nullptr)
{
connect(elementScene(), &ElementScene::elementInfoChanged,
this, &PartDynamicTextField::elementInfoChanged);
m_first_add = false;
}
}
else if ((change == QGraphicsItem::ItemSelectedHasChanged) && (value.toBool() == true))
updateCurrentPartEditor();
return(QGraphicsTextItem::itemChange(change, value));
}
void PartDynamicTextField::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) {
QGraphicsTextItem::paint(painter, option, widget);
if (m_frame) {
painter -> save();
painter -> setFont(this -> font());
//Adjust the thickness according to the font size,
qreal w=0.3;
if(this -> font().pointSize() >= 5) {
w = this -> font().pointSizeF()*0.1;
if(w > 2.5)
w = 2.5;
}
QPen pen;
pen.setColor(color());
pen.setWidthF(w);
painter -> setPen(pen);
painter -> setRenderHint(QPainter::Antialiasing);
//Get the bounding rectangle of the text
QSizeF size = document() -> size();
size.setWidth(document() -> idealWidth());
//Remove the margin. Size is exactly the bounding rect of the text
size.rheight() -= document() -> documentMargin()*2;
size.rwidth() -= document() -> documentMargin()*2;
//Add a little margin only for a better visual;
size.rheight() += 2;
size.rwidth() += 2;
//The pos of the rect
QPointF pos = boundingRect().center();
pos.rx() -= size.width()/2;
pos.ry() -= size.height()/2;
//Adjust the rounding of the rectangle according to the size of the font
qreal ro = this -> font().pointSizeF()/3;
painter -> drawRoundedRect(QRectF(pos, size), ro, ro);
painter -> restore();
}
}
/**
@brief PartDynamicTextField::elementInfoChanged
Used to up to date this text field,
when the element information (see elementScene) changed
*/
void PartDynamicTextField::elementInfoChanged()
{
if(!elementScene())
return;
if(m_text_from == DynamicElementTextItem::ElementInfo)
setPlainText(elementScene()->elementData().m_informations.value(m_info_name).toString());
else if (m_text_from == DynamicElementTextItem::CompositeText && elementScene())
setPlainText(autonum::AssignVariables::replaceVariable(
m_composite_text, elementScene()->elementData().m_informations));
}
void PartDynamicTextField::prepareAlignment()
{
m_alignment_rect = boundingRect();
}
void PartDynamicTextField::finishAlignment()
{
if(m_block_alignment)
return;
QTransform transform;
transform.rotate(this -> rotation());
qreal x,xa, y,ya;
x=xa=0;
y=ya=0;
if(m_alignment &Qt::AlignRight) {
x = m_alignment_rect.right();
xa = boundingRect().right();
}
else if(m_alignment &Qt::AlignHCenter) {
x = m_alignment_rect.center().x();
xa = boundingRect().center().x();
}
if(m_alignment &Qt::AlignBottom) {
y = m_alignment_rect.bottom();
ya = boundingRect().bottom();
}
else if(m_alignment &Qt::AlignVCenter) {
y = m_alignment_rect.center().y();
ya = boundingRect().center().y();
}
QPointF p = transform.map(QPointF(x,y));
QPointF pa = transform.map(QPointF(xa,ya));
QPointF diff = pa-p;
setPos(this -> pos() - diff);
}