mirror of
https://github.com/qelectrotech/qelectrotech-source-mirror.git
synced 2026-10-22 06:14:14 +02:00
Place symbol texts: label and notes as one block, at one of nine points
A new command, "Place symbol texts", for the selected symbols: their label and the notes beside it go as one block next to the drawing of the symbol, 3 px away, at one of the nine points of the text alignment dialog, chosen in a 3 x 3 grid that opens from the toolbar button and from the Edit menu. A click on the button places them at the point chosen last (left of the symbol at first). Left of the symbol the lines are aligned right, so that they hang on it and a longer text grows away from it (their alignment is set too); right of it aligned left; above, below and on it centred. The lines keep their order and their spacing, a group of texts is placed as one line with the same alignment, a turned symbol is placed in the folio's directions, and everything is one undo step. Element::drawingRect() gives the area the drawing covers, tighter than the bounding rect, which is rounded up to the grid. A toolbar command with a menu now shows as a button with an arrow beside it. Translations for all languages. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -17,6 +17,9 @@
|
||||
*/
|
||||
#include "diagramtoolbarsettings.h"
|
||||
|
||||
#include <QMenu>
|
||||
#include <QToolButton>
|
||||
|
||||
#include "shortcutmanager.h"
|
||||
|
||||
#include <QApplication>
|
||||
@@ -198,7 +201,9 @@ QStringList DiagramToolbarSettings::defaultIds(const QString &name)
|
||||
//the shapes of the "Ajouter" toolbar.
|
||||
sep,
|
||||
QStringLiteral("diagrameditor.add_cable"),
|
||||
QStringLiteral("widget:conductor_color")};
|
||||
QStringLiteral("widget:conductor_color"),
|
||||
sep,
|
||||
QStringLiteral("diagrameditor.place_symbol_texts")};
|
||||
}
|
||||
if (name == QLatin1String("adding")) {
|
||||
//add_pdf is skipped when built without QtPdf, as before
|
||||
@@ -330,6 +335,11 @@ void DiagramToolbarSettings::fill(QToolBar *toolbar, const QStringList &ids,
|
||||
pending_separator = false;
|
||||
}
|
||||
toolbar->addAction(action);
|
||||
//A command with a menu of choices: a click does it again,
|
||||
//the arrow beside it opens the choices
|
||||
if (action->menu())
|
||||
if (auto *button = qobject_cast<QToolButton *>(toolbar->widgetForAction(action)))
|
||||
button->setPopupMode(QToolButton::MenuButtonPopup);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
#include "container/qetzip.h"
|
||||
#include "utils/previousversions.h"
|
||||
#include "shownkinds.h"
|
||||
#include "symboltextplacement.h"
|
||||
#ifdef QET_HAS_SCRIPTING
|
||||
#include "scripting/qetscripting.h"
|
||||
#include "scripting/scriptlibrary.h"
|
||||
@@ -33,6 +34,7 @@
|
||||
#include <QCoreApplication>
|
||||
#include <QToolButton>
|
||||
#include <QWidgetAction>
|
||||
#include <QGridLayout>
|
||||
#include "ElementsCollection/elementscollectionwidget.h"
|
||||
#include "ElementsCollection/elementpickerpopup.h"
|
||||
#include "shortcutbarsettings.h"
|
||||
@@ -489,6 +491,57 @@ void QETDiagramEditor::setUpActions()
|
||||
connect(m_copy_text_format, &QAction::triggered, this, &QETDiagramEditor::copyTextFormat);
|
||||
connect(m_apply_text_format, &QAction::triggered, this, &QETDiagramEditor::applyTextFormat);
|
||||
|
||||
//The texts of the selected symbols, label and notes, as one block at
|
||||
//one of nine points around the symbol: the nine points of the text
|
||||
//alignment dialog. A click places them at the point chosen last.
|
||||
m_place_texts = new QAction(QET::Icons::AlignRight, tr("Place symbol texts"), this);
|
||||
m_place_texts->setStatusTip(tr("Place the label and the notes of the selected symbols as one block beside them", "status bar tip"));
|
||||
ShortcutManager::instance().registerAction(m_place_texts, "diagrameditor.place_symbol_texts", tr("Diagram editors"), QKeySequence());
|
||||
{
|
||||
auto *menu = new QMenu(this);
|
||||
auto *grid_widget = new QWidget(menu);
|
||||
auto *grid = new QGridLayout(grid_widget);
|
||||
grid->setContentsMargins(6, 6, 6, 6);
|
||||
grid->setSpacing(2);
|
||||
const struct { Qt::Alignment where; const char *arrow; const char *tip; } points[] = {
|
||||
{Qt::AlignLeft | Qt::AlignTop, "\u2196", QT_TR_NOOP("Above, left")},
|
||||
{Qt::AlignHCenter | Qt::AlignTop, "\u2191", QT_TR_NOOP("Above")},
|
||||
{Qt::AlignRight | Qt::AlignTop, "\u2197", QT_TR_NOOP("Above, right")},
|
||||
{Qt::AlignLeft | Qt::AlignVCenter, "\u2190", QT_TR_NOOP("Left")},
|
||||
{Qt::AlignCenter, "\u25CF", QT_TR_NOOP("On the symbol")},
|
||||
{Qt::AlignRight | Qt::AlignVCenter, "\u2192", QT_TR_NOOP("Right")},
|
||||
{Qt::AlignLeft | Qt::AlignBottom, "\u2199", QT_TR_NOOP("Below, left")},
|
||||
{Qt::AlignHCenter | Qt::AlignBottom, "\u2193", QT_TR_NOOP("Below")},
|
||||
{Qt::AlignRight | Qt::AlignBottom, "\u2198", QT_TR_NOOP("Below, right")}};
|
||||
int n = 0;
|
||||
for (const auto &point : points)
|
||||
{
|
||||
auto *button = new QToolButton(grid_widget);
|
||||
button->setText(QString::fromUtf8(point.arrow));
|
||||
button->setToolTip(tr(point.tip));
|
||||
button->setObjectName(QStringLiteral("place_texts_%1").arg(n));
|
||||
button->setFixedSize(28, 28);
|
||||
button->setAutoRaise(true);
|
||||
const Qt::Alignment where = point.where;
|
||||
connect(button, &QToolButton::clicked, this, [this, menu, where]() {
|
||||
menu->close();
|
||||
QSettings().setValue(QStringLiteral("diagrameditor/place_texts_point"), int(where));
|
||||
placeSymbolTexts(where);
|
||||
});
|
||||
grid->addWidget(button, n / 3, n % 3);
|
||||
++n;
|
||||
}
|
||||
auto *grid_action = new QWidgetAction(menu);
|
||||
grid_action->setDefaultWidget(grid_widget);
|
||||
menu->addAction(grid_action);
|
||||
m_place_texts->setMenu(menu);
|
||||
}
|
||||
connect(m_place_texts, &QAction::triggered, this, [this]() {
|
||||
const int where = QSettings().value(QStringLiteral("diagrameditor/place_texts_point"),
|
||||
int(Qt::AlignLeft | Qt::AlignVCenter)).toInt();
|
||||
placeSymbolTexts(Qt::Alignment(where));
|
||||
});
|
||||
|
||||
//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):
|
||||
@@ -1585,6 +1638,7 @@ void QETDiagramEditor::setUpMenu()
|
||||
menu_edition -> addAction(m_paste);
|
||||
menu_edition -> addAction(m_copy_text_format);
|
||||
menu_edition -> addAction(m_apply_text_format);
|
||||
menu_edition -> addAction(m_place_texts);
|
||||
menu_edition -> addAction(m_paste_origin);
|
||||
menu_edition -> addAction(m_paste_element_info);
|
||||
menu_edition -> addAction(m_duplicate);
|
||||
@@ -3187,6 +3241,7 @@ void QETDiagramEditor::slot_updateComplexActions()
|
||||
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_place_texts->setEnabled(!ro && selected_elements_count >= 1);
|
||||
m_mirror_horizontal->setEnabled(!ro && selected_elements_count);
|
||||
m_mirror_vertical->setEnabled(!ro && selected_elements_count);
|
||||
|
||||
@@ -5014,6 +5069,27 @@ void QETDiagramEditor::copyTextFormat()
|
||||
* @brief QETDiagramEditor::applyTextFormat
|
||||
* Applies the stored text formatting template to all selected elements in the active diagram.
|
||||
*/
|
||||
/**
|
||||
@brief QETDiagramEditor::placeSymbolTexts
|
||||
Place the texts of the selected symbols as one block at @a where,
|
||||
one of nine points around each symbol, in one undo step.
|
||||
@param where : see SymbolTextPlacement::place()
|
||||
*/
|
||||
void QETDiagramEditor::placeSymbolTexts(Qt::Alignment where)
|
||||
{
|
||||
DiagramView *dv = currentDiagramView();
|
||||
if (!dv || !dv->diagram() || dv->diagram()->isReadOnly()) return;
|
||||
|
||||
QList<Element *> elements;
|
||||
for (QGraphicsItem *item : dv->diagram()->selectedItems())
|
||||
if (Element *element = qgraphicsitem_cast<Element *>(item))
|
||||
elements.append(element);
|
||||
if (elements.isEmpty()) return;
|
||||
|
||||
if (QUndoCommand *undo = SymbolTextPlacement::place(elements, where))
|
||||
dv->diagram()->undoStack().push(undo);
|
||||
}
|
||||
|
||||
void QETDiagramEditor::applyTextFormat()
|
||||
{
|
||||
if (!ElementTextFormatPattern::hasPattern()) return;
|
||||
|
||||
@@ -210,6 +210,7 @@ class QETDiagramEditor : public QETMainWindow
|
||||
void subWindowActivated(QMdiSubWindow *subWindows);
|
||||
void copyTextFormat();
|
||||
void applyTextFormat();
|
||||
void placeSymbolTexts(Qt::Alignment where);
|
||||
|
||||
private slots:
|
||||
void updateTextGridButton();
|
||||
@@ -225,7 +226,8 @@ class QETDiagramEditor : public QETMainWindow
|
||||
*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_copy_text_format = nullptr, ///< Copy label/text formatting template
|
||||
*m_apply_text_format = nullptr; ///< Apply label/text formatting template
|
||||
*m_apply_text_format = nullptr, ///< Apply label/text formatting template
|
||||
*m_place_texts = nullptr; ///< Place the texts of the selected symbols at one of nine points
|
||||
|
||||
QActionGroup
|
||||
m_row_column_actions_group, /// Action related to add/remove rows/column in diagram
|
||||
|
||||
@@ -598,6 +598,18 @@ void Element::paint(
|
||||
/**
|
||||
@return Le rectangle delimitant le contour de l'element
|
||||
*/
|
||||
/**
|
||||
@brief Element::drawingRect
|
||||
@return the area its drawing covers, in this symbol's coordinates:
|
||||
tighter than boundingRect(), which is rounded up to the grid. The
|
||||
bounding rect for a symbol with nothing drawn.
|
||||
*/
|
||||
QRectF Element::drawingRect() const
|
||||
{
|
||||
const QRect drawn = m_picture.isNull() ? QRect() : m_picture.boundingRect();
|
||||
return drawn.isEmpty() ? boundingRect() : QRectF(drawn);
|
||||
}
|
||||
|
||||
QRectF Element::boundingRect() const
|
||||
{
|
||||
return(QRectF(QPointF(-hotspot_coord.x(), -hotspot_coord.y()),
|
||||
|
||||
@@ -185,6 +185,8 @@ class Element : public QetGraphicsItem
|
||||
QPixmap pixmap();
|
||||
QPoint setHotspot(QPoint);
|
||||
QPoint hotspot() const;
|
||||
/// The area the symbol is drawn on, in its own coordinates
|
||||
QRectF drawingRect() const;
|
||||
void editProperty() override;
|
||||
static bool valideXml(QDomElement &);
|
||||
static QUuid savedTerminalUuid(const QList<QDomElement> &records, int index);
|
||||
|
||||
@@ -0,0 +1,129 @@
|
||||
/*
|
||||
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 "symboltextplacement.h"
|
||||
|
||||
#include "QPropertyUndoCommand/qpropertyundocommand.h"
|
||||
#include "qetgraphicsitem/dynamicelementtextitem.h"
|
||||
#include "qetgraphicsitem/element.h"
|
||||
#include "qetgraphicsitem/elementtextitemgroup.h"
|
||||
|
||||
#include <QCoreApplication>
|
||||
#include <QUndoCommand>
|
||||
|
||||
namespace {
|
||||
|
||||
/// How the lines of the block line up, for a block placed at @a where
|
||||
Qt::Alignment linesFor(Qt::Alignment where)
|
||||
{
|
||||
if (where & Qt::AlignLeft) return Qt::AlignRight;
|
||||
if (where & Qt::AlignRight) return Qt::AlignLeft;
|
||||
return Qt::AlignHCenter;
|
||||
}
|
||||
|
||||
/// One line of the block: a text, or a group of texts
|
||||
struct Line
|
||||
{
|
||||
QObject *object; ///< for its properties
|
||||
QGraphicsItem *item; ///< for where it stands
|
||||
QPointF old_pos;
|
||||
QVariant old_alignment;
|
||||
};
|
||||
|
||||
/// Move @a item so that the top left of its box in the folio is at @a to
|
||||
void moveTo(QGraphicsItem *item, const QPointF &to)
|
||||
{
|
||||
const QPointF by = to - item->sceneBoundingRect().topLeft();
|
||||
QGraphicsItem *parent = item->parentItem();
|
||||
const QPointF step = parent ? parent->mapFromScene(by) - parent->mapFromScene(QPointF(0, 0))
|
||||
: by;
|
||||
item->setPos(item->pos() + step);
|
||||
}
|
||||
|
||||
bool placeOne(Element *element, Qt::Alignment where, QUndoCommand *parent)
|
||||
{
|
||||
const Qt::Alignment lines_alignment = linesFor(where);
|
||||
|
||||
QList<Line> lines;
|
||||
for (DynamicElementTextItem *text : element->dynamicTextItems())
|
||||
if (!text->parentGroup() && text->isVisible())
|
||||
lines.append({text, text, text->pos(), text->property("alignment")});
|
||||
for (ElementTextItemGroup *group : element->textGroups())
|
||||
if (group->isVisible())
|
||||
lines.append({group, group, group->pos(), group->property("alignment")});
|
||||
if (lines.isEmpty())
|
||||
return false;
|
||||
|
||||
//The lines first take the alignment of the block: a group lays its
|
||||
//texts out anew, and later changes of a text keep that side.
|
||||
for (const Line &line : lines)
|
||||
{
|
||||
Qt::Alignment a = line.old_alignment.value<Qt::Alignment>();
|
||||
if (qobject_cast<ElementTextItemGroup *>(line.object))
|
||||
a = lines_alignment == Qt::AlignHCenter ? Qt::AlignVCenter : lines_alignment;
|
||||
else
|
||||
a = (a & Qt::AlignVertical_Mask) | lines_alignment;
|
||||
line.object->setProperty("alignment", QVariant::fromValue(a));
|
||||
}
|
||||
|
||||
QRectF block;
|
||||
for (const Line &line : lines)
|
||||
block |= line.item->sceneBoundingRect();
|
||||
|
||||
const QRectF body = element->mapRectToScene(element->drawingRect());
|
||||
const qreal gap = SymbolTextPlacement::gap;
|
||||
QRectF to = block;
|
||||
if (where & Qt::AlignLeft) to.moveRight(body.left() - gap);
|
||||
else if (where & Qt::AlignRight) to.moveLeft(body.right() + gap);
|
||||
else to.moveLeft(body.center().x() - block.width() / 2);
|
||||
if (where & Qt::AlignTop) to.moveBottom(body.top() - gap);
|
||||
else if (where & Qt::AlignBottom) to.moveTop(body.bottom() + gap);
|
||||
else to.moveTop(body.center().y() - block.height() / 2);
|
||||
|
||||
for (const Line &line : lines)
|
||||
{
|
||||
const QRectF box = line.item->sceneBoundingRect();
|
||||
const qreal y = to.top() + box.top() - block.top();
|
||||
const qreal x = lines_alignment == Qt::AlignRight ? to.right() - box.width()
|
||||
: lines_alignment == Qt::AlignLeft ? to.left()
|
||||
: to.center().x() - box.width() / 2;
|
||||
moveTo(line.item, QPointF(x, y));
|
||||
|
||||
const QVariant alignment = line.object->property("alignment");
|
||||
if (alignment != line.old_alignment)
|
||||
new QPropertyUndoCommand(line.object, "alignment", line.old_alignment, alignment, parent);
|
||||
if (line.item->pos() != line.old_pos)
|
||||
new QPropertyUndoCommand(line.object, "pos", line.old_pos, line.item->pos(), parent);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
QUndoCommand *SymbolTextPlacement::place(const QList<Element *> &elements, Qt::Alignment where)
|
||||
{
|
||||
auto *undo = new QUndoCommand(
|
||||
QCoreApplication::translate("SymbolTextPlacement", "Place symbol texts"));
|
||||
bool any = false;
|
||||
for (Element *element : elements)
|
||||
any |= placeOne(element, where, undo);
|
||||
if (!any || !undo->childCount()) {
|
||||
delete undo;
|
||||
return nullptr;
|
||||
}
|
||||
return undo;
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
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/>.
|
||||
*/
|
||||
#ifndef SYMBOLTEXTPLACEMENT_H
|
||||
#define SYMBOLTEXTPLACEMENT_H
|
||||
|
||||
#include <QList>
|
||||
#include <Qt>
|
||||
|
||||
class Element;
|
||||
class QUndoCommand;
|
||||
|
||||
/**
|
||||
Place the texts of a symbol -- its label and the notes beside it -- as
|
||||
one block at one of nine points around its drawing, the nine of the
|
||||
text alignment dialog. Left of the symbol, the lines are aligned right,
|
||||
so that the block hangs on the symbol and a longer text grows away
|
||||
from it; right of it, aligned left; above, below and on it, centred.
|
||||
The lines keep their order and their spacing; a group of texts is
|
||||
placed as one line of the block, with the same alignment.
|
||||
*/
|
||||
namespace SymbolTextPlacement
|
||||
{
|
||||
/// Room between the drawing of the symbol and the block
|
||||
constexpr double gap = 3;
|
||||
|
||||
/**
|
||||
@param elements : the symbols whose texts to place
|
||||
@param where : the side of the drawing, as a 9-point alignment:
|
||||
Qt::AlignLeft | Qt::AlignVCenter is left of the symbol, halfway
|
||||
up; Qt::AlignCenter is on it
|
||||
@return one undo step that places them (already placed), or
|
||||
nullptr when none of the symbols has a text
|
||||
*/
|
||||
QUndoCommand *place(const QList<Element *> &elements, Qt::Alignment where);
|
||||
}
|
||||
|
||||
#endif // SYMBOLTEXTPLACEMENT_H
|
||||
Reference in New Issue
Block a user