mirror of
https://github.com/qelectrotech/qelectrotech-source-mirror.git
synced 2026-10-17 11:24:14 +02:00
Merge pull request #1465 from itseogo/feature/text-format-painter-and-chained-reports
Add Element Text Format Painter tool
This commit is contained in:
@@ -0,0 +1,13 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24">
|
||||
<!-- Paint brush handle -->
|
||||
<path d="M19.7 4.3a2.5 2.5 0 0 0-3.5 0l-7.7 7.7a3.5 3.5 0 0 0-.9 1.6l-.6 2.4 2.4-.6a3.5 3.5 0 0 0 1.6-.9l7.7-7.7a2.5 2.5 0 0 0 0-3.5z"
|
||||
fill="#f39c12" stroke="#d68910" stroke-width="0.8" stroke-linejoin="round"/>
|
||||
<!-- Ferrule (metal band) -->
|
||||
<path d="M14.5 9.5 l-2-2 l1.2-1.2 l2 2 z" fill="#bdc3c7"/>
|
||||
<!-- Brush tip / bristles with active paint -->
|
||||
<path d="M7 16c-1.5.5-3.5 1.5-4 4 1.5 0 3.5-.5 4.5-1.5s1-2.5-.5-2.5z"
|
||||
fill="#27ae60" stroke="#1e8449" stroke-width="0.5"/>
|
||||
<!-- Paint stroke / apply check badge in bottom-right -->
|
||||
<circle cx="17.5" cy="17.5" r="5" fill="#27ae60" stroke="#ffffff" stroke-width="1.2"/>
|
||||
<polyline points="15.2,17.5 16.8,19.2 20,15.8" fill="none" stroke="#ffffff" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 916 B |
@@ -0,0 +1,15 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24">
|
||||
<!-- Paint brush handle -->
|
||||
<path d="M19.7 4.3a2.5 2.5 0 0 0-3.5 0l-7.7 7.7a3.5 3.5 0 0 0-.9 1.6l-.6 2.4 2.4-.6a3.5 3.5 0 0 0 1.6-.9l7.7-7.7a2.5 2.5 0 0 0 0-3.5z"
|
||||
fill="#f39c12" stroke="#d68910" stroke-width="0.8" stroke-linejoin="round"/>
|
||||
<!-- Ferrule (metal band) -->
|
||||
<path d="M14.5 9.5 l-2-2 l1.2-1.2 l2 2 z" fill="#bdc3c7"/>
|
||||
<!-- Brush tip / bristles -->
|
||||
<path d="M7 16c-1.5.5-3.5 1.5-4 4 1.5 0 3.5-.5 4.5-1.5s1-2.5-.5-2.5z"
|
||||
fill="#2980b9" stroke="#1f618d" stroke-width="0.5"/>
|
||||
<!-- Copy badge: two overlapping small rectangles in bottom-right -->
|
||||
<rect x="13" y="13" width="7" height="8" rx="1.2" fill="#ffffff" stroke="#2c3e50" stroke-width="1.2"/>
|
||||
<rect x="16" y="16" width="7" height="8" rx="1.2" fill="#3498db" stroke="#2c3e50" stroke-width="1.2"/>
|
||||
<line x1="18" y1="19" x2="21" y2="19" stroke="#ffffff" stroke-width="1" stroke-linecap="round"/>
|
||||
<line x1="18" y1="21" x2="21" y2="21" stroke="#ffffff" stroke-width="1" stroke-linecap="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
@@ -11864,6 +11864,41 @@ Verfügbare Optionen:
|
||||
</context>
|
||||
<context>
|
||||
<name>QETDiagramEditor</name>
|
||||
<message>
|
||||
<location filename="../sources/qetdiagrameditor.cpp" line="468"/>
|
||||
<source>Copy text format</source>
|
||||
<translation>Beschriftungsart kopieren</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../sources/qetdiagrameditor.cpp" line="469"/>
|
||||
<source>Apply text format</source>
|
||||
<translation>Beschriftungsart zuweisen</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../sources/qetdiagrameditor.cpp" line="474"/>
|
||||
<source>Copy the text format of the selected element as a template</source>
|
||||
<comment>status bar tip</comment>
|
||||
<translation>Kopiert das Beschriftungsformat des ausgewählten Elements als Vorlage</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../sources/qetdiagrameditor.cpp" line="475"/>
|
||||
<source>Apply the copied text format to the selected elements</source>
|
||||
<comment>status bar tip</comment>
|
||||
<translation>Weist das kopierte Beschriftungsformat den markierten Elementen zu</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../sources/qetdiagrameditor.cpp" line="4720"/>
|
||||
<source>Text format of "%1" copied</source>
|
||||
<translation>Beschriftungsart von „%1“ kopiert</translation>
|
||||
</message>
|
||||
<message numerus="yes">
|
||||
<location filename="../sources/qetdiagrameditor.cpp" line="4748"/>
|
||||
<source>Text format applied to %n element(s)</source>
|
||||
<translation>
|
||||
<numerusform>Beschriftungsart auf %n Element zugewiesen</numerusform>
|
||||
<numerusform>Beschriftungsart auf %n Elemente zugewiesen</numerusform>
|
||||
</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../sources/qetdiagrameditor.cpp" line="175"/>
|
||||
<location filename="../sources/qetdiagrameditor.cpp" line="3956"/>
|
||||
@@ -15231,6 +15266,11 @@ Was möchten Sie tun?</translation>
|
||||
</context>
|
||||
<context>
|
||||
<name>QObject</name>
|
||||
<message>
|
||||
<location filename="../sources/elementtextpattern.cpp" line="432"/>
|
||||
<source>Apply text format</source>
|
||||
<translation>Beschriftungsart zuweisen</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../sources/factory/elementpicturefactory.cpp" line="312"/>
|
||||
<location filename="../sources/qetgraphicsitem/element.cpp" line="677"/>
|
||||
|
||||
@@ -11873,6 +11873,41 @@ Options disponibles :
|
||||
</context>
|
||||
<context>
|
||||
<name>QETDiagramEditor</name>
|
||||
<message>
|
||||
<location filename="../sources/qetdiagrameditor.cpp" line="468"/>
|
||||
<source>Copy text format</source>
|
||||
<translation>Copier le style de texte</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../sources/qetdiagrameditor.cpp" line="469"/>
|
||||
<source>Apply text format</source>
|
||||
<translation>Appliquer le style de texte</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../sources/qetdiagrameditor.cpp" line="474"/>
|
||||
<source>Copy the text format of the selected element as a template</source>
|
||||
<comment>status bar tip</comment>
|
||||
<translation>Copie le format de texte de l'élément sélectionné comme modèle</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../sources/qetdiagrameditor.cpp" line="475"/>
|
||||
<source>Apply the copied text format to the selected elements</source>
|
||||
<comment>status bar tip</comment>
|
||||
<translation>Applique le format de texte copié aux éléments sélectionnés</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../sources/qetdiagrameditor.cpp" line="4720"/>
|
||||
<source>Text format of "%1" copied</source>
|
||||
<translation>Style de texte de « %1 » copié</translation>
|
||||
</message>
|
||||
<message numerus="yes">
|
||||
<location filename="../sources/qetdiagrameditor.cpp" line="4748"/>
|
||||
<source>Text format applied to %n element(s)</source>
|
||||
<translation>
|
||||
<numerusform>Style de texte appliqué à %n élément</numerusform>
|
||||
<numerusform>Style de texte appliqué à %n éléments</numerusform>
|
||||
</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../sources/qetdiagrameditor.cpp" line="175"/>
|
||||
<location filename="../sources/qetdiagrameditor.cpp" line="3956"/>
|
||||
@@ -15246,6 +15281,11 @@ Que désirez vous faire ?</translation>
|
||||
</context>
|
||||
<context>
|
||||
<name>QObject</name>
|
||||
<message>
|
||||
<location filename="../sources/elementtextpattern.cpp" line="432"/>
|
||||
<source>Apply text format</source>
|
||||
<translation>Appliquer le style de texte</translation>
|
||||
</message>
|
||||
<message numerus="yes">
|
||||
<location filename="../sources/qet.cpp" line="352"/>
|
||||
<source>%n elements</source>
|
||||
|
||||
@@ -467,5 +467,7 @@
|
||||
<file>licenses/QElectroTech.LICENSE</file>
|
||||
<file>licenses/QET-Elements.NOTICE</file>
|
||||
<file>licenses/QET-Elements.LICENSE</file>
|
||||
<file>ico/format-copy.svg</file>
|
||||
<file>ico/format-apply.svg</file>
|
||||
</qresource>
|
||||
</RCC>
|
||||
|
||||
@@ -2138,6 +2138,8 @@ QList<QAction *> DiagramView::contextMenuActions() const
|
||||
list << qde->m_cut;
|
||||
list << qde->m_copy;
|
||||
list << m_multi_paste;
|
||||
list << qde->m_copy_text_format;
|
||||
list << qde->m_apply_text_format;
|
||||
list << qde->m_paste_element_info;
|
||||
list << m_separators.at(0);
|
||||
list << m_create_template; // Add the create template action
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
#include "ui/importelementtextpatterndialog.h"
|
||||
#include "undocommand/addelementtextcommand.h"
|
||||
#include "undocommand/deleteqgraphicsitemcommand.h"
|
||||
#include "shownkinds.h"
|
||||
|
||||
#include <QDir>
|
||||
#include <QGraphicsScene>
|
||||
@@ -272,3 +273,635 @@ void ImportElementTextPattern::apply(QString name, bool erase) const
|
||||
|
||||
undo_stack.endMacro();
|
||||
}
|
||||
|
||||
//=============================================================================
|
||||
// ElementTextFormatPattern & ApplyElementTextFormatCommand (Format Painter)
|
||||
//=============================================================================
|
||||
|
||||
#include "terminal.h"
|
||||
|
||||
ElementTextFormatPattern ElementTextFormatPattern::s_current_pattern;
|
||||
bool ElementTextFormatPattern::s_has_pattern = false;
|
||||
|
||||
bool ElementTextFormatPattern::hasPattern()
|
||||
{
|
||||
return s_has_pattern && s_current_pattern.isValid();
|
||||
}
|
||||
|
||||
const ElementTextFormatPattern &ElementTextFormatPattern::currentPattern()
|
||||
{
|
||||
return s_current_pattern;
|
||||
}
|
||||
|
||||
void ElementTextFormatPattern::setCurrentPattern(const ElementTextFormatPattern &pattern)
|
||||
{
|
||||
s_current_pattern = pattern;
|
||||
s_has_pattern = pattern.isValid();
|
||||
}
|
||||
|
||||
ElementTextFormatPattern ElementTextFormatPattern::fromElement(Element *element)
|
||||
{
|
||||
ElementTextFormatPattern pattern;
|
||||
if (!element) return pattern;
|
||||
|
||||
pattern.m_source_element_name = element->name();
|
||||
|
||||
// 1. Capture groups
|
||||
for (ElementTextItemGroup *group : element->textGroups()) {
|
||||
TextGroupFormatItem gItem;
|
||||
gItem.name = group->name();
|
||||
gItem.pos = group->pos();
|
||||
gItem.rotation = group->rotation();
|
||||
gItem.alignment = group->alignment();
|
||||
gItem.verticalAdjustment = group->verticalAdjustment();
|
||||
gItem.frame = group->frame();
|
||||
gItem.holdToBottomPage = group->holdToBottomPage();
|
||||
pattern.m_groups.append(gItem);
|
||||
}
|
||||
|
||||
// 2. Capture all dynamic texts
|
||||
int idx = 0;
|
||||
// Ungrouped texts
|
||||
for (DynamicElementTextItem *deti : element->dynamicTextItems()) {
|
||||
DynamicTextFormatItem tItem;
|
||||
tItem.textFrom = deti->textFrom();
|
||||
tItem.infoName = deti->infoName();
|
||||
tItem.compositeText = deti->toPlainText();
|
||||
tItem.font = deti->font();
|
||||
tItem.color = deti->color();
|
||||
tItem.alignment = deti->alignment();
|
||||
tItem.pos = deti->pos();
|
||||
tItem.rotation = deti->rotation();
|
||||
tItem.keepVisualRotation = deti->keepVisualRotation();
|
||||
tItem.rotationPointCenter = deti->rotationPointCenter();
|
||||
tItem.frame = deti->frame();
|
||||
tItem.textWidth = deti->textWidth();
|
||||
tItem.visible = ShownKinds::wantsVisible(deti);
|
||||
tItem.groupName = QString();
|
||||
tItem.index = idx++;
|
||||
tItem.isTerminalText = isTerminalTextItem(deti, element);
|
||||
pattern.m_text_items.append(tItem);
|
||||
}
|
||||
|
||||
// Grouped texts
|
||||
for (ElementTextItemGroup *group : element->textGroups()) {
|
||||
for (DynamicElementTextItem *deti : group->texts()) {
|
||||
DynamicTextFormatItem tItem;
|
||||
tItem.textFrom = deti->textFrom();
|
||||
tItem.infoName = deti->infoName();
|
||||
tItem.compositeText = deti->toPlainText();
|
||||
tItem.font = deti->font();
|
||||
tItem.color = deti->color();
|
||||
tItem.alignment = deti->alignment();
|
||||
tItem.pos = deti->pos();
|
||||
tItem.rotation = deti->rotation();
|
||||
tItem.keepVisualRotation = deti->keepVisualRotation();
|
||||
tItem.rotationPointCenter = deti->rotationPointCenter();
|
||||
tItem.frame = deti->frame();
|
||||
tItem.textWidth = deti->textWidth();
|
||||
tItem.visible = ShownKinds::wantsVisible(deti);
|
||||
tItem.groupName = group->name();
|
||||
tItem.index = idx++;
|
||||
tItem.isTerminalText = isTerminalTextItem(deti, element);
|
||||
pattern.m_text_items.append(tItem);
|
||||
}
|
||||
}
|
||||
|
||||
return pattern;
|
||||
}
|
||||
|
||||
bool ElementTextFormatPattern::hasTerminalTextFormat() const
|
||||
{
|
||||
for (const DynamicTextFormatItem &item : m_text_items) {
|
||||
if (item.isTerminalText) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
DynamicTextFormatItem ElementTextFormatPattern::terminalTextFormat() const
|
||||
{
|
||||
for (const DynamicTextFormatItem &item : m_text_items) {
|
||||
if (item.isTerminalText) return item;
|
||||
}
|
||||
return DynamicTextFormatItem();
|
||||
}
|
||||
|
||||
bool ElementTextFormatPattern::isTerminalTextItem(const DynamicElementTextItem *deti, const Element *element)
|
||||
{
|
||||
if (!deti || !element) return false;
|
||||
if (deti->textFrom() != DynamicElementTextItem::UserText) return false;
|
||||
|
||||
const QString text = deti->toPlainText().trimmed();
|
||||
if (text.isEmpty()) return false;
|
||||
|
||||
const QList<Terminal *> terms = element->terminals();
|
||||
for (const Terminal *t : terms) {
|
||||
if (!t) continue;
|
||||
if (t->name().trimmed() == text) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Secondary check: text is short (<= 6 chars, typical pin designation)
|
||||
// and located close to a terminal (within 40 pixels)
|
||||
if (text.length() <= 6) {
|
||||
for (const Terminal *t : terms) {
|
||||
if (!t) continue;
|
||||
QPointF diff = deti->pos() - t->pos();
|
||||
if ((diff.x() * diff.x() + diff.y() * diff.y()) <= (40.0 * 40.0)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
ApplyElementTextFormatCommand::ApplyElementTextFormatCommand(
|
||||
Element *element,
|
||||
const ElementTextFormatPattern &pattern,
|
||||
QUndoCommand *parent)
|
||||
: ApplyElementTextFormatCommand(QList<Element *>{element}, pattern, parent)
|
||||
{
|
||||
}
|
||||
|
||||
ApplyElementTextFormatCommand::ApplyElementTextFormatCommand(
|
||||
const QList<Element *> &elements,
|
||||
const ElementTextFormatPattern &pattern,
|
||||
QUndoCommand *parent)
|
||||
: QUndoCommand(parent)
|
||||
{
|
||||
setText(QObject::tr("Apply text format"));
|
||||
|
||||
for (Element *element : elements) {
|
||||
if (!element) continue;
|
||||
|
||||
ElementFormatChange elChange;
|
||||
elChange.element = element;
|
||||
|
||||
// Collect all existing texts on target element
|
||||
QList<DynamicElementTextItem *> targetTexts;
|
||||
targetTexts << element->dynamicTextItems();
|
||||
for (ElementTextItemGroup *g : element->textGroups()) {
|
||||
targetTexts << g->texts();
|
||||
}
|
||||
|
||||
// Setup group changes
|
||||
for (const TextGroupFormatItem &gItem : pattern.groups()) {
|
||||
ElementFormatChange::GroupChange gc;
|
||||
gc.name = gItem.name;
|
||||
ElementTextItemGroup *existingGroup = element->textGroup(gItem.name);
|
||||
if (existingGroup) {
|
||||
gc.group = existingGroup;
|
||||
gc.wasCreated = false;
|
||||
gc.oldPos = existingGroup->pos();
|
||||
gc.oldRotation = existingGroup->rotation();
|
||||
gc.oldAlignment = existingGroup->alignment();
|
||||
gc.oldVAdjust = existingGroup->verticalAdjustment();
|
||||
gc.oldFrame = existingGroup->frame();
|
||||
gc.oldHoldBottom = existingGroup->holdToBottomPage();
|
||||
} else {
|
||||
gc.group = nullptr;
|
||||
gc.wasCreated = true;
|
||||
}
|
||||
gc.newPos = gItem.pos;
|
||||
gc.newRotation = gItem.rotation;
|
||||
gc.newAlignment = gItem.alignment;
|
||||
gc.newVAdjust = gItem.verticalAdjustment;
|
||||
gc.newFrame = gItem.frame;
|
||||
gc.newHoldBottom = gItem.holdToBottomPage;
|
||||
elChange.groupChanges.append(gc);
|
||||
}
|
||||
|
||||
// Text matching
|
||||
QSet<DynamicElementTextItem *> matchedTarget;
|
||||
QSet<int> matchedPatternIndices;
|
||||
|
||||
// Identify terminal / pole texts on the target element
|
||||
QSet<DynamicElementTextItem *> targetTerminalTexts;
|
||||
for (DynamicElementTextItem *tDeti : targetTexts) {
|
||||
if (ElementTextFormatPattern::isTerminalTextItem(tDeti, element)) {
|
||||
targetTerminalTexts.insert(tDeti);
|
||||
}
|
||||
}
|
||||
|
||||
// Handle Terminal / Pole texts:
|
||||
// They belong to physical terminals/poles of the element (e.g. 1..6, A1/A2, 13/14).
|
||||
// Their positions, rotations, frames and visibilities are NEVER modified!
|
||||
// If the pattern provides a terminal text style (font, color), apply it uniformly to all terminal texts.
|
||||
const bool hasTermFmt = pattern.hasTerminalTextFormat();
|
||||
const DynamicTextFormatItem termFmt = hasTermFmt ? pattern.terminalTextFormat() : DynamicTextFormatItem();
|
||||
|
||||
for (DynamicElementTextItem *tDeti : targetTerminalTexts) {
|
||||
matchedTarget.insert(tDeti); // Exclude from general user text matching
|
||||
|
||||
// Only record a change if font or color actually changes
|
||||
if (hasTermFmt && (tDeti->font() != termFmt.font || tDeti->color() != termFmt.color)) {
|
||||
ElementFormatChange::TextChange tc;
|
||||
tc.deti = tDeti;
|
||||
tc.wasCreated = false;
|
||||
tc.oldFont = tDeti->font();
|
||||
tc.oldColor = tDeti->color();
|
||||
tc.oldAlignment = tDeti->alignment();
|
||||
tc.oldPos = tDeti->pos();
|
||||
tc.oldRotation = tDeti->rotation();
|
||||
tc.oldKeepVisualRotation = tDeti->keepVisualRotation();
|
||||
tc.oldRotationPointCenter = tDeti->rotationPointCenter();
|
||||
tc.oldFrame = tDeti->frame();
|
||||
tc.oldTextWidth = tDeti->textWidth();
|
||||
tc.oldVisible = ShownKinds::wantsVisible(tDeti);
|
||||
tc.oldGroupName = tDeti->parentGroup() ? tDeti->parentGroup()->name() : QString();
|
||||
|
||||
tc.newFont = termFmt.font;
|
||||
tc.newColor = termFmt.color;
|
||||
// Positions, alignments, rotations, frames and visibilities are strictly preserved on terminals!
|
||||
tc.newAlignment = tc.oldAlignment;
|
||||
tc.newPos = tc.oldPos;
|
||||
tc.newRotation = tc.oldRotation;
|
||||
tc.newKeepVisualRotation = tc.oldKeepVisualRotation;
|
||||
tc.newRotationPointCenter = tc.oldRotationPointCenter;
|
||||
tc.newFrame = tc.oldFrame;
|
||||
tc.newTextWidth = tc.oldTextWidth;
|
||||
tc.newVisible = tc.oldVisible; // Always retain visibility!
|
||||
tc.newGroupName = tc.oldGroupName;
|
||||
|
||||
elChange.textChanges.append(tc);
|
||||
}
|
||||
}
|
||||
|
||||
// Pass 1: ElementInfo with same infoName
|
||||
for (int i = 0; i < pattern.textItems().size(); ++i) {
|
||||
const DynamicTextFormatItem &pItem = pattern.textItems().at(i);
|
||||
if (pItem.textFrom == DynamicElementTextItem::ElementInfo && !pItem.infoName.isEmpty()) {
|
||||
for (DynamicElementTextItem *tDeti : targetTexts) {
|
||||
if (!matchedTarget.contains(tDeti) &&
|
||||
tDeti->textFrom() == DynamicElementTextItem::ElementInfo &&
|
||||
tDeti->infoName() == pItem.infoName) {
|
||||
|
||||
matchedTarget.insert(tDeti);
|
||||
matchedPatternIndices.insert(i);
|
||||
|
||||
ElementFormatChange::TextChange tc;
|
||||
tc.deti = tDeti;
|
||||
tc.wasCreated = false;
|
||||
tc.oldFont = tDeti->font();
|
||||
tc.oldColor = tDeti->color();
|
||||
tc.oldAlignment = tDeti->alignment();
|
||||
tc.oldPos = tDeti->pos();
|
||||
tc.oldRotation = tDeti->rotation();
|
||||
tc.oldKeepVisualRotation = tDeti->keepVisualRotation();
|
||||
tc.oldRotationPointCenter = tDeti->rotationPointCenter();
|
||||
tc.oldFrame = tDeti->frame();
|
||||
tc.oldTextWidth = tDeti->textWidth();
|
||||
tc.oldVisible = ShownKinds::wantsVisible(tDeti);
|
||||
tc.oldGroupName = tDeti->parentGroup() ? tDeti->parentGroup()->name() : QString();
|
||||
|
||||
tc.newFont = pItem.font;
|
||||
tc.newColor = pItem.color;
|
||||
tc.newAlignment = pItem.alignment;
|
||||
tc.newPos = pItem.pos;
|
||||
tc.newRotation = pItem.rotation;
|
||||
tc.newKeepVisualRotation = pItem.keepVisualRotation;
|
||||
tc.newRotationPointCenter = pItem.rotationPointCenter;
|
||||
tc.newFrame = pItem.frame;
|
||||
tc.newTextWidth = pItem.textWidth;
|
||||
tc.newVisible = pItem.visible;
|
||||
tc.newGroupName = pItem.groupName;
|
||||
|
||||
elChange.textChanges.append(tc);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Pass 2: UserText (non-terminal texts only)
|
||||
for (int i = 0; i < pattern.textItems().size(); ++i) {
|
||||
if (matchedPatternIndices.contains(i)) continue;
|
||||
const DynamicTextFormatItem &pItem = pattern.textItems().at(i);
|
||||
if (pItem.isTerminalText) continue; // Skip terminal texts in generic user text matching
|
||||
if (pItem.textFrom == DynamicElementTextItem::UserText) {
|
||||
DynamicElementTextItem *foundDeti = nullptr;
|
||||
for (DynamicElementTextItem *tDeti : targetTexts) {
|
||||
if (!matchedTarget.contains(tDeti) &&
|
||||
tDeti->textFrom() == DynamicElementTextItem::UserText &&
|
||||
!pItem.compositeText.isEmpty() &&
|
||||
tDeti->toPlainText() == pItem.compositeText) {
|
||||
foundDeti = tDeti;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!foundDeti) {
|
||||
for (DynamicElementTextItem *tDeti : targetTexts) {
|
||||
if (!matchedTarget.contains(tDeti) &&
|
||||
tDeti->textFrom() == DynamicElementTextItem::UserText) {
|
||||
foundDeti = tDeti;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (foundDeti) {
|
||||
matchedTarget.insert(foundDeti);
|
||||
matchedPatternIndices.insert(i);
|
||||
|
||||
ElementFormatChange::TextChange tc;
|
||||
tc.deti = foundDeti;
|
||||
tc.wasCreated = false;
|
||||
tc.oldFont = foundDeti->font();
|
||||
tc.oldColor = foundDeti->color();
|
||||
tc.oldAlignment = foundDeti->alignment();
|
||||
tc.oldPos = foundDeti->pos();
|
||||
tc.oldRotation = foundDeti->rotation();
|
||||
tc.oldKeepVisualRotation = foundDeti->keepVisualRotation();
|
||||
tc.oldRotationPointCenter = foundDeti->rotationPointCenter();
|
||||
tc.oldFrame = foundDeti->frame();
|
||||
tc.oldTextWidth = foundDeti->textWidth();
|
||||
tc.oldVisible = ShownKinds::wantsVisible(foundDeti);
|
||||
tc.oldGroupName = foundDeti->parentGroup() ? foundDeti->parentGroup()->name() : QString();
|
||||
|
||||
tc.newFont = pItem.font;
|
||||
tc.newColor = pItem.color;
|
||||
tc.newAlignment = pItem.alignment;
|
||||
tc.newPos = pItem.pos;
|
||||
tc.newRotation = pItem.rotation;
|
||||
tc.newKeepVisualRotation = pItem.keepVisualRotation;
|
||||
tc.newRotationPointCenter = pItem.rotationPointCenter;
|
||||
tc.newFrame = pItem.frame;
|
||||
tc.newTextWidth = pItem.textWidth;
|
||||
tc.newVisible = pItem.visible;
|
||||
tc.newGroupName = pItem.groupName;
|
||||
|
||||
elChange.textChanges.append(tc);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Pass 3: CompositeText or other unmatched (non-terminal)
|
||||
for (int i = 0; i < pattern.textItems().size(); ++i) {
|
||||
if (matchedPatternIndices.contains(i)) continue;
|
||||
const DynamicTextFormatItem &pItem = pattern.textItems().at(i);
|
||||
if (pItem.isTerminalText) continue;
|
||||
DynamicElementTextItem *foundDeti = nullptr;
|
||||
for (DynamicElementTextItem *tDeti : targetTexts) {
|
||||
if (!matchedTarget.contains(tDeti) && tDeti->textFrom() == pItem.textFrom) {
|
||||
foundDeti = tDeti;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (foundDeti) {
|
||||
matchedTarget.insert(foundDeti);
|
||||
matchedPatternIndices.insert(i);
|
||||
|
||||
ElementFormatChange::TextChange tc;
|
||||
tc.deti = foundDeti;
|
||||
tc.wasCreated = false;
|
||||
tc.oldFont = foundDeti->font();
|
||||
tc.oldColor = foundDeti->color();
|
||||
tc.oldAlignment = foundDeti->alignment();
|
||||
tc.oldPos = foundDeti->pos();
|
||||
tc.oldRotation = foundDeti->rotation();
|
||||
tc.oldKeepVisualRotation = foundDeti->keepVisualRotation();
|
||||
tc.oldRotationPointCenter = foundDeti->rotationPointCenter();
|
||||
tc.oldFrame = foundDeti->frame();
|
||||
tc.oldTextWidth = foundDeti->textWidth();
|
||||
tc.oldVisible = ShownKinds::wantsVisible(foundDeti);
|
||||
tc.oldGroupName = foundDeti->parentGroup() ? foundDeti->parentGroup()->name() : QString();
|
||||
|
||||
tc.newFont = pItem.font;
|
||||
tc.newColor = pItem.color;
|
||||
tc.newAlignment = pItem.alignment;
|
||||
tc.newPos = pItem.pos;
|
||||
tc.newRotation = pItem.rotation;
|
||||
tc.newKeepVisualRotation = pItem.keepVisualRotation;
|
||||
tc.newRotationPointCenter = pItem.rotationPointCenter;
|
||||
tc.newFrame = pItem.frame;
|
||||
tc.newTextWidth = pItem.textWidth;
|
||||
tc.newVisible = pItem.visible;
|
||||
tc.newGroupName = pItem.groupName;
|
||||
|
||||
elChange.textChanges.append(tc);
|
||||
}
|
||||
}
|
||||
|
||||
// Pass 4: Pattern ElementInfo items that don't exist in target element
|
||||
for (int i = 0; i < pattern.textItems().size(); ++i) {
|
||||
if (matchedPatternIndices.contains(i)) continue;
|
||||
const DynamicTextFormatItem &pItem = pattern.textItems().at(i);
|
||||
if (pItem.textFrom == DynamicElementTextItem::ElementInfo && !pItem.infoName.isEmpty()) {
|
||||
ElementFormatChange::TextChange tc;
|
||||
tc.deti = nullptr;
|
||||
tc.wasCreated = true;
|
||||
tc.newFont = pItem.font;
|
||||
tc.newColor = pItem.color;
|
||||
tc.newAlignment = pItem.alignment;
|
||||
tc.newPos = pItem.pos;
|
||||
tc.newRotation = pItem.rotation;
|
||||
tc.newKeepVisualRotation = pItem.keepVisualRotation;
|
||||
tc.newRotationPointCenter = pItem.rotationPointCenter;
|
||||
tc.newFrame = pItem.frame;
|
||||
tc.newTextWidth = pItem.textWidth;
|
||||
tc.newVisible = pItem.visible;
|
||||
tc.newGroupName = pItem.groupName;
|
||||
tc.oldGroupName = pItem.infoName; // infoName stored temporarily in oldGroupName
|
||||
elChange.textChanges.append(tc);
|
||||
}
|
||||
}
|
||||
|
||||
// Pass 5 (Destructive hiding of unmatched texts) is completely removed:
|
||||
// Non-matching target texts (such as additional poles on 3-pole/4-pole elements)
|
||||
// are never hidden and retain their exact positions and visibilities!
|
||||
|
||||
m_changes.append(elChange);
|
||||
}
|
||||
}
|
||||
|
||||
ApplyElementTextFormatCommand::~ApplyElementTextFormatCommand()
|
||||
{
|
||||
for (ElementFormatChange &elChange : m_changes) {
|
||||
Element *element = elChange.element.data();
|
||||
for (ElementFormatChange::TextChange &tc : elChange.textChanges) {
|
||||
if (tc.wasCreated && tc.deti) {
|
||||
DynamicElementTextItem *deti = tc.deti.data();
|
||||
if (!deti) continue;
|
||||
if (deti->parentGroup()) continue;
|
||||
if (!element || !element->dynamicTextItems().contains(deti)) {
|
||||
delete deti;
|
||||
}
|
||||
}
|
||||
}
|
||||
for (ElementFormatChange::GroupChange &gc : elChange.groupChanges) {
|
||||
if (gc.wasCreated && gc.group) {
|
||||
ElementTextItemGroup *group = gc.group.data();
|
||||
if (!group) continue;
|
||||
if (!element || !element->textGroups().contains(group)) {
|
||||
delete group;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ApplyElementTextFormatCommand::redo()
|
||||
{
|
||||
for (ElementFormatChange &elChange : m_changes) {
|
||||
Element *element = elChange.element.data();
|
||||
if (!element) continue;
|
||||
|
||||
// 1. Create and update groups
|
||||
for (ElementFormatChange::GroupChange &gc : elChange.groupChanges) {
|
||||
ElementTextItemGroup *group = gc.group.data();
|
||||
if (!group) {
|
||||
group = element->textGroup(gc.name);
|
||||
if (!group) {
|
||||
group = element->addTextGroup(gc.name);
|
||||
}
|
||||
gc.group = group;
|
||||
} else {
|
||||
if (!element->textGroups().contains(group)) {
|
||||
element->addTextGroup(group);
|
||||
}
|
||||
}
|
||||
if (group) {
|
||||
group->setPos(gc.newPos);
|
||||
group->setRotation(gc.newRotation);
|
||||
group->setAlignment(gc.newAlignment);
|
||||
group->setVerticalAdjustment(gc.newVAdjust);
|
||||
group->setFrame(gc.newFrame);
|
||||
group->setHoldToBottomPage(gc.newHoldBottom);
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Apply texts
|
||||
for (ElementFormatChange::TextChange &tc : elChange.textChanges) {
|
||||
DynamicElementTextItem *deti = tc.deti.data();
|
||||
if (tc.wasCreated) {
|
||||
if (!deti) {
|
||||
deti = new DynamicElementTextItem(element);
|
||||
deti->setTextFrom(DynamicElementTextItem::ElementInfo);
|
||||
deti->setInfoName(tc.oldGroupName);
|
||||
element->addDynamicTextItem(deti);
|
||||
tc.deti = deti;
|
||||
} else {
|
||||
deti->setParentItem(element);
|
||||
if (!element->dynamicTextItems().contains(deti)) {
|
||||
element->addDynamicTextItem(deti);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!deti) continue;
|
||||
|
||||
// Group membership
|
||||
if (!tc.newGroupName.isEmpty()) {
|
||||
ElementTextItemGroup *targetGroup = element->textGroup(tc.newGroupName);
|
||||
if (targetGroup) {
|
||||
if (deti->parentGroup() != targetGroup) {
|
||||
if (deti->parentGroup()) {
|
||||
element->removeTextFromGroup(deti, deti->parentGroup());
|
||||
}
|
||||
element->addTextToGroup(deti, targetGroup);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (deti->parentGroup()) {
|
||||
element->removeTextFromGroup(deti, deti->parentGroup());
|
||||
}
|
||||
}
|
||||
|
||||
// Apply properties
|
||||
deti->setFont(tc.newFont);
|
||||
deti->setColor(tc.newColor);
|
||||
deti->setAlignment(tc.newAlignment);
|
||||
deti->setPos(tc.newPos);
|
||||
deti->setRotation(tc.newRotation);
|
||||
deti->setKeepVisualRotation(tc.newKeepVisualRotation);
|
||||
deti->setRotationPointCenter(tc.newRotationPointCenter);
|
||||
deti->setFrame(tc.newFrame);
|
||||
deti->setTextWidth(tc.newTextWidth);
|
||||
ShownKinds::setVisible(deti, tc.newVisible);
|
||||
deti->update();
|
||||
}
|
||||
|
||||
element->update();
|
||||
if (element->diagram()) {
|
||||
element->diagram()->update();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ApplyElementTextFormatCommand::undo()
|
||||
{
|
||||
for (ElementFormatChange &elChange : m_changes) {
|
||||
Element *element = elChange.element.data();
|
||||
if (!element) continue;
|
||||
|
||||
// 1. Restore texts
|
||||
for (ElementFormatChange::TextChange &tc : elChange.textChanges) {
|
||||
DynamicElementTextItem *deti = tc.deti.data();
|
||||
if (!deti) continue;
|
||||
|
||||
if (tc.wasCreated) {
|
||||
if (deti->parentGroup()) {
|
||||
element->removeTextFromGroup(deti, deti->parentGroup());
|
||||
}
|
||||
element->removeDynamicTextItem(deti);
|
||||
if (deti->scene()) {
|
||||
deti->scene()->removeItem(deti);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// Restore group membership
|
||||
if (!tc.oldGroupName.isEmpty()) {
|
||||
ElementTextItemGroup *origGroup = element->textGroup(tc.oldGroupName);
|
||||
if (origGroup) {
|
||||
if (deti->parentGroup() != origGroup) {
|
||||
if (deti->parentGroup()) {
|
||||
element->removeTextFromGroup(deti, deti->parentGroup());
|
||||
}
|
||||
element->addTextToGroup(deti, origGroup);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (deti->parentGroup()) {
|
||||
element->removeTextFromGroup(deti, deti->parentGroup());
|
||||
}
|
||||
}
|
||||
|
||||
// Restore properties
|
||||
deti->setFont(tc.oldFont);
|
||||
deti->setColor(tc.oldColor);
|
||||
deti->setAlignment(tc.oldAlignment);
|
||||
deti->setPos(tc.oldPos);
|
||||
deti->setRotation(tc.oldRotation);
|
||||
deti->setKeepVisualRotation(tc.oldKeepVisualRotation);
|
||||
deti->setRotationPointCenter(tc.oldRotationPointCenter);
|
||||
deti->setFrame(tc.oldFrame);
|
||||
deti->setTextWidth(tc.oldTextWidth);
|
||||
ShownKinds::setVisible(deti, tc.oldVisible);
|
||||
deti->update();
|
||||
}
|
||||
|
||||
// 2. Restore groups
|
||||
for (ElementFormatChange::GroupChange &gc : elChange.groupChanges) {
|
||||
ElementTextItemGroup *group = gc.group.data();
|
||||
if (!group) continue;
|
||||
|
||||
if (gc.wasCreated) {
|
||||
element->removeTextGroup(group);
|
||||
continue;
|
||||
}
|
||||
|
||||
group->setPos(gc.oldPos);
|
||||
group->setRotation(gc.oldRotation);
|
||||
group->setAlignment(gc.oldAlignment);
|
||||
group->setVerticalAdjustment(gc.oldVAdjust);
|
||||
group->setFrame(gc.oldFrame);
|
||||
group->setHoldToBottomPage(gc.oldHoldBottom);
|
||||
}
|
||||
|
||||
element->update();
|
||||
if (element->diagram()) {
|
||||
element->diagram()->update();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,9 +20,17 @@
|
||||
|
||||
#include <QString>
|
||||
#include <QDomDocument>
|
||||
#include <QFont>
|
||||
#include <QColor>
|
||||
#include <QPointF>
|
||||
#include <QList>
|
||||
#include <QUndoCommand>
|
||||
#include <QPointer>
|
||||
#include "qetgraphicsitem/dynamicelementtextitem.h"
|
||||
|
||||
class Element;
|
||||
class QWidget;
|
||||
class ElementTextItemGroup;
|
||||
|
||||
class ExportElementTextPattern
|
||||
{
|
||||
@@ -55,4 +63,153 @@ class ImportElementTextPattern
|
||||
Element *m_element = nullptr;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Description of formatting and layout for an element text item.
|
||||
*/
|
||||
struct DynamicTextFormatItem {
|
||||
DynamicElementTextItem::TextFrom textFrom = DynamicElementTextItem::UserText;
|
||||
QString infoName;
|
||||
QString compositeText;
|
||||
QFont font;
|
||||
QColor color = Qt::black;
|
||||
Qt::Alignment alignment = (Qt::AlignLeft | Qt::AlignTop);
|
||||
QPointF pos;
|
||||
qreal rotation = 0;
|
||||
bool keepVisualRotation = false;
|
||||
bool rotationPointCenter = false;
|
||||
bool frame = false;
|
||||
qreal textWidth = -1;
|
||||
bool visible = true;
|
||||
QString groupName;
|
||||
int index = 0;
|
||||
bool isTerminalText = false;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Description of formatting and layout for an element text group.
|
||||
*/
|
||||
struct TextGroupFormatItem {
|
||||
QString name;
|
||||
QPointF pos;
|
||||
qreal rotation = 0;
|
||||
Qt::Alignment alignment = Qt::AlignLeft;
|
||||
int verticalAdjustment = 0;
|
||||
bool frame = false;
|
||||
bool holdToBottomPage = false;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Stores the formatting pattern (font, size, style, colors, alignment, positions, offsets, frame)
|
||||
* captured from a source Element. Acts as the Format Painter template clipboard.
|
||||
*/
|
||||
class ElementTextFormatPattern
|
||||
{
|
||||
public:
|
||||
ElementTextFormatPattern() = default;
|
||||
|
||||
static ElementTextFormatPattern fromElement(Element *element);
|
||||
static bool hasPattern();
|
||||
static const ElementTextFormatPattern ¤tPattern();
|
||||
static void setCurrentPattern(const ElementTextFormatPattern &pattern);
|
||||
static bool isTerminalTextItem(const DynamicElementTextItem *deti, const Element *element);
|
||||
|
||||
bool isValid() const { return !m_text_items.isEmpty(); }
|
||||
QString sourceElementName() const { return m_source_element_name; }
|
||||
|
||||
const QList<DynamicTextFormatItem> &textItems() const { return m_text_items; }
|
||||
const QList<TextGroupFormatItem> &groups() const { return m_groups; }
|
||||
|
||||
bool hasTerminalTextFormat() const;
|
||||
DynamicTextFormatItem terminalTextFormat() const;
|
||||
|
||||
private:
|
||||
QString m_source_element_name;
|
||||
QList<DynamicTextFormatItem> m_text_items;
|
||||
QList<TextGroupFormatItem> m_groups;
|
||||
|
||||
static ElementTextFormatPattern s_current_pattern;
|
||||
static bool s_has_pattern;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Undo command to apply a text formatting pattern to one or multiple elements.
|
||||
* Field content (labels, designations, comments, article numbers) is preserved unchanged.
|
||||
*/
|
||||
class ApplyElementTextFormatCommand : public QUndoCommand
|
||||
{
|
||||
public:
|
||||
ApplyElementTextFormatCommand(const QList<Element *> &elements,
|
||||
const ElementTextFormatPattern &pattern,
|
||||
QUndoCommand *parent = nullptr);
|
||||
ApplyElementTextFormatCommand(Element *element,
|
||||
const ElementTextFormatPattern &pattern,
|
||||
QUndoCommand *parent = nullptr);
|
||||
~ApplyElementTextFormatCommand() override;
|
||||
|
||||
void undo() override;
|
||||
void redo() override;
|
||||
|
||||
private:
|
||||
struct ElementFormatChange {
|
||||
QPointer<Element> element;
|
||||
|
||||
struct TextChange {
|
||||
QPointer<DynamicElementTextItem> deti;
|
||||
bool wasCreated = false;
|
||||
|
||||
// Old state
|
||||
QFont oldFont;
|
||||
QColor oldColor;
|
||||
Qt::Alignment oldAlignment;
|
||||
QPointF oldPos;
|
||||
qreal oldRotation = 0;
|
||||
bool oldKeepVisualRotation = false;
|
||||
bool oldRotationPointCenter = false;
|
||||
bool oldFrame = false;
|
||||
qreal oldTextWidth = -1;
|
||||
bool oldVisible = true;
|
||||
QString oldGroupName;
|
||||
|
||||
// New state
|
||||
QFont newFont;
|
||||
QColor newColor;
|
||||
Qt::Alignment newAlignment;
|
||||
QPointF newPos;
|
||||
qreal newRotation = 0;
|
||||
bool newKeepVisualRotation = false;
|
||||
bool newRotationPointCenter = false;
|
||||
bool newFrame = false;
|
||||
qreal newTextWidth = -1;
|
||||
bool newVisible = true;
|
||||
QString newGroupName;
|
||||
};
|
||||
QList<TextChange> textChanges;
|
||||
|
||||
struct GroupChange {
|
||||
QString name;
|
||||
QPointer<ElementTextItemGroup> group;
|
||||
bool wasCreated = false;
|
||||
|
||||
// Old state
|
||||
QPointF oldPos;
|
||||
qreal oldRotation = 0;
|
||||
Qt::Alignment oldAlignment = Qt::AlignLeft;
|
||||
int oldVAdjust = 0;
|
||||
bool oldFrame = false;
|
||||
bool oldHoldBottom = false;
|
||||
|
||||
// New state
|
||||
QPointF newPos;
|
||||
qreal newRotation = 0;
|
||||
Qt::Alignment newAlignment = Qt::AlignLeft;
|
||||
int newVAdjust = 0;
|
||||
bool newFrame = false;
|
||||
bool newHoldBottom = false;
|
||||
};
|
||||
QList<GroupChange> groupChanges;
|
||||
};
|
||||
|
||||
QList<ElementFormatChange> m_changes;
|
||||
};
|
||||
|
||||
#endif // ELEMENTTEXTCONFIGURATION_H
|
||||
|
||||
@@ -53,6 +53,7 @@
|
||||
#include "cable/editcablecommand.h"
|
||||
#include "diagramevent/diagrameventaddpaste.h"
|
||||
#include "diagramview.h"
|
||||
#include "elementtextpattern.h"
|
||||
#include "elementspanelwidget.h"
|
||||
#include "factory/elementpicturefactory.h"
|
||||
#include "factory/qetgraphicstablefactory.h"
|
||||
@@ -469,6 +470,19 @@ void QETDiagramEditor::setUpActions()
|
||||
connect(m_paste_element_info, &QAction::triggered,
|
||||
this, &QETDiagramEditor::pasteElementInformations);
|
||||
|
||||
//Format painter actions
|
||||
m_copy_text_format = new QAction(QET::Icons::CopyElementTextFormat, tr("Copy text format"), this);
|
||||
m_apply_text_format = new QAction(QET::Icons::ApplyElementTextFormat, tr("Apply text format"), this);
|
||||
|
||||
ShortcutManager::instance().registerAction(m_copy_text_format, "diagrameditor.copy_text_format", tr("Diagram editors"), QKeySequence(Qt::CTRL | Qt::ALT | Qt::Key_C));
|
||||
ShortcutManager::instance().registerAction(m_apply_text_format, "diagrameditor.apply_text_format", tr("Diagram editors"), QKeySequence(Qt::CTRL | Qt::ALT | Qt::Key_V));
|
||||
|
||||
m_copy_text_format -> setStatusTip(tr("Copy the text format of the selected element as a template", "status bar tip"));
|
||||
m_apply_text_format -> setStatusTip(tr("Apply the copied text format to the selected elements", "status bar tip"));
|
||||
|
||||
connect(m_copy_text_format, &QAction::triggered, this, &QETDiagramEditor::copyTextFormat);
|
||||
connect(m_apply_text_format, &QAction::triggered, this, &QETDiagramEditor::applyTextFormat);
|
||||
|
||||
//Duplicate: copy the selection and place it at a configured,
|
||||
//grid-step offset immediately -- no interactive follow-the-
|
||||
//cursor step, unlike Ctrl+V above. That is deliberate (#991):
|
||||
@@ -1550,6 +1564,8 @@ void QETDiagramEditor::setUpMenu()
|
||||
menu_edition -> addAction(m_cut);
|
||||
menu_edition -> addAction(m_copy);
|
||||
menu_edition -> addAction(m_paste);
|
||||
menu_edition -> addAction(m_copy_text_format);
|
||||
menu_edition -> addAction(m_apply_text_format);
|
||||
menu_edition -> addAction(m_paste_origin);
|
||||
menu_edition -> addAction(m_paste_element_info);
|
||||
menu_edition -> addAction(m_duplicate);
|
||||
@@ -2903,6 +2919,8 @@ void QETDiagramEditor::slot_updateComplexActions()
|
||||
// number of selected elements
|
||||
int selected_elements_count = dc.count(DiagramContent::Elements);
|
||||
m_find_element->setEnabled(selected_elements_count == 1);
|
||||
m_copy_text_format->setEnabled(selected_elements_count >= 1);
|
||||
m_apply_text_format->setEnabled(!ro && selected_elements_count >= 1 && ElementTextFormatPattern::hasPattern());
|
||||
m_mirror_horizontal->setEnabled(!ro && selected_elements_count);
|
||||
m_mirror_vertical->setEnabled(!ro && selected_elements_count);
|
||||
|
||||
@@ -4723,3 +4741,56 @@ void QETDiagramEditor::updateTextGridButton()
|
||||
//A divisor the menu does not offer, set by hand in the config file
|
||||
m_text_grid_button->setText(tr("Texts %1").arg(TextGrid::ratioLabel(divisor)));
|
||||
}
|
||||
/**
|
||||
* @brief QETDiagramEditor::copyTextFormat
|
||||
* Copies the text/label layout and formatting from the currently selected element as a template.
|
||||
*/
|
||||
void QETDiagramEditor::copyTextFormat()
|
||||
{
|
||||
DiagramView *dv = currentDiagramView();
|
||||
if (!dv || !dv->diagram()) return;
|
||||
|
||||
Element *sourceElmt = nullptr;
|
||||
for (QGraphicsItem *item : dv->diagram()->selectedItems()) {
|
||||
if (Element *elmt = qgraphicsitem_cast<Element *>(item)) {
|
||||
sourceElmt = elmt;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!sourceElmt) return;
|
||||
|
||||
ElementTextFormatPattern pattern = ElementTextFormatPattern::fromElement(sourceElmt);
|
||||
if (pattern.isValid()) {
|
||||
ElementTextFormatPattern::setCurrentPattern(pattern);
|
||||
slot_updateComplexActions();
|
||||
statusBar()->showMessage(tr("Text format of \"%1\" copied").arg(sourceElmt->name()), 3000);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief QETDiagramEditor::applyTextFormat
|
||||
* Applies the stored text formatting template to all selected elements in the active diagram.
|
||||
*/
|
||||
void QETDiagramEditor::applyTextFormat()
|
||||
{
|
||||
if (!ElementTextFormatPattern::hasPattern()) return;
|
||||
|
||||
DiagramView *dv = currentDiagramView();
|
||||
if (!dv || !dv->diagram()) return;
|
||||
if (dv->diagram()->isReadOnly()) return;
|
||||
|
||||
QList<Element *> targetElements;
|
||||
for (QGraphicsItem *item : dv->diagram()->selectedItems()) {
|
||||
if (Element *elmt = qgraphicsitem_cast<Element *>(item)) {
|
||||
targetElements.append(elmt);
|
||||
}
|
||||
}
|
||||
|
||||
if (targetElements.isEmpty()) return;
|
||||
|
||||
const ElementTextFormatPattern &pattern = ElementTextFormatPattern::currentPattern();
|
||||
dv->diagram()->undoStack().push(new ApplyElementTextFormatCommand(targetElements, pattern));
|
||||
|
||||
statusBar()->showMessage(tr("Text format applied to %n element(s)", "", targetElements.size()), 3000);
|
||||
}
|
||||
|
||||
@@ -202,6 +202,8 @@ class QETDiagramEditor : public QETMainWindow
|
||||
void showError(const QETResult &);
|
||||
void showError(const QString &);
|
||||
void subWindowActivated(QMdiSubWindow *subWindows);
|
||||
void copyTextFormat();
|
||||
void applyTextFormat();
|
||||
|
||||
private slots:
|
||||
void updateTextGridButton();
|
||||
@@ -215,7 +217,9 @@ class QETDiagramEditor : public QETMainWindow
|
||||
*m_cut, ///< Cut selection to clipboard
|
||||
*m_copy, ///< Copy selection to clipboard
|
||||
*m_paste_element_info, ///< Copy the information of the copied element onto the selected elements (#1375)
|
||||
*m_insert_last_element = nullptr; ///< Place the last placed element again
|
||||
*m_insert_last_element = nullptr, ///< Place the last placed element again
|
||||
*m_copy_text_format = nullptr, ///< Copy label/text formatting template
|
||||
*m_apply_text_format = nullptr; ///< Apply label/text formatting template
|
||||
|
||||
QActionGroup
|
||||
m_row_column_actions_group, /// Action related to add/remove rows/column in diagram
|
||||
|
||||
@@ -75,6 +75,8 @@ namespace QET {
|
||||
QIcon EditDelete;
|
||||
QIcon EditOpacity;
|
||||
QIcon EditPaste;
|
||||
QIcon CopyElementTextFormat;
|
||||
QIcon ApplyElementTextFormat;
|
||||
QIcon EditRedo;
|
||||
QIcon EditRename;
|
||||
QIcon EditSelectAll;
|
||||
@@ -464,6 +466,8 @@ void QET::Icons::initIcons()
|
||||
EditDelete = QIcon::fromTheme("edit-delete");
|
||||
EditOpacity = QIcon::fromTheme("edit-opacity");
|
||||
EditPaste = QIcon::fromTheme("edit-paste");
|
||||
CopyElementTextFormat = QIcon(":/ico/format-copy.svg");
|
||||
ApplyElementTextFormat = QIcon(":/ico/format-apply.svg");
|
||||
if (rtl) {
|
||||
EditRedo.addPixmap(QIcon::fromTheme("edit-redo").pixmap(16).transformed(reverse));
|
||||
EditRedo.addPixmap(QIcon::fromTheme("edit-redo").pixmap(22).transformed(reverse));
|
||||
|
||||
@@ -82,6 +82,8 @@ namespace QET {
|
||||
extern QIcon EditDelete;
|
||||
extern QIcon EditOpacity;
|
||||
extern QIcon EditPaste;
|
||||
extern QIcon CopyElementTextFormat;
|
||||
extern QIcon ApplyElementTextFormat;
|
||||
extern QIcon EditRedo;
|
||||
extern QIcon EditRename;
|
||||
extern QIcon EditSelectAll;
|
||||
|
||||
Reference in New Issue
Block a user