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:
@@ -0,0 +1,154 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
// Placing the texts of a symbol at one of nine points, linked against the
|
||||
// application's objects.
|
||||
//
|
||||
// "Place symbol texts" puts the label and the notes of a symbol as one
|
||||
// block beside its drawing, 3 px away, at one of the nine points of the
|
||||
// text alignment dialog. Left of the symbol the lines are aligned right,
|
||||
// so that they hang on it and a longer text grows away from it; right of
|
||||
// it aligned left; above it centred. The lines keep their order and their
|
||||
// spacing, a group of texts is placed as one line, a turned symbol is
|
||||
// placed in the folio's directions, and one undo puts everything back.
|
||||
#include <QApplication>
|
||||
#include <QFontDatabase>
|
||||
#include <QSettings>
|
||||
#include <QTemporaryDir>
|
||||
#include <QUndoCommand>
|
||||
#include <cstdio>
|
||||
#include <stdexcept>
|
||||
|
||||
#include "../../sources/diagram.h"
|
||||
#include "../../sources/qetproject.h"
|
||||
#include "../../sources/qetmessagebox.h"
|
||||
#include "../../sources/symboltextplacement.h"
|
||||
#include "../../sources/qetgraphicsitem/element.h"
|
||||
#include "../../sources/qetgraphicsitem/dynamicelementtextitem.h"
|
||||
#include "../../sources/qetgraphicsitem/elementtextitemgroup.h"
|
||||
|
||||
static void check(bool value, const char *message)
|
||||
{
|
||||
if (!value) throw std::runtime_error(message);
|
||||
std::printf("ok: %s\n", message);
|
||||
}
|
||||
|
||||
static bool same(qreal a, qreal b) { return qAbs(a - b) < 0.6; }
|
||||
|
||||
static QRectF body(Element *e) { return e->mapRectToScene(e->drawingRect()); }
|
||||
|
||||
static QRectF block(const QList<DynamicElementTextItem *> &texts)
|
||||
{
|
||||
QRectF r;
|
||||
for (DynamicElementTextItem *t : texts) r |= t->sceneBoundingRect();
|
||||
return r;
|
||||
}
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
QApplication app(argc, argv);
|
||||
std::freopen(argv[2], "w", stdout);
|
||||
|
||||
QTemporaryDir settings;
|
||||
QSettings::setDefaultFormat(QSettings::IniFormat);
|
||||
QSettings::setPath(QSettings::IniFormat, QSettings::UserScope, settings.path());
|
||||
QCoreApplication::setOrganizationName("QETSymbolTextPlacementRegression");
|
||||
QETProject::setBackupEnabled(false);
|
||||
QET::QetMessageBox::setNonInteractive(true);
|
||||
QFontDatabase::addApplicationFont(":/fonts/LiberationSans-Regular.ttf");
|
||||
|
||||
try
|
||||
{
|
||||
QETProject project(QString::fromLocal8Bit(argv[1]));
|
||||
check(project.state() == QETProject::Ok, "fixture opens");
|
||||
project.buildFolios(project.diagrams());
|
||||
Diagram *scene = project.diagrams().first();
|
||||
Element *element = scene->elements().first();
|
||||
check(element && !element->drawingRect().isEmpty(), "the fixture has a drawn symbol");
|
||||
check(element->drawingRect().width() <= element->boundingRect().width(),
|
||||
"the drawing is no wider than the frame");
|
||||
|
||||
//The symbol's own texts and three of ours, to the right of it
|
||||
for (DynamicElementTextItem *t : element->dynamicTextItems()) t->setVisible(false);
|
||||
QList<DynamicElementTextItem *> texts;
|
||||
const QStringList lines {"-FI1", "40 A / 30 mA, type A", "FI UG"};
|
||||
for (int i = 0; i < lines.size(); ++i) {
|
||||
auto *t = new DynamicElementTextItem(element);
|
||||
t->setTextFrom(DynamicElementTextItem::UserText);
|
||||
t->setText(lines[i]);
|
||||
t->setPos(80 + 5 * i, -20 + 12 * i);
|
||||
element->addDynamicTextItem(t);
|
||||
texts << t;
|
||||
}
|
||||
QList<QPointF> start;
|
||||
for (DynamicElementTextItem *t : texts) start << t->pos();
|
||||
const QRectF before = block(texts);
|
||||
|
||||
// Left of the symbol, halfway up: aligned right, 3 px away.
|
||||
QUndoCommand *undo = SymbolTextPlacement::place({element}, Qt::AlignLeft | Qt::AlignVCenter);
|
||||
check(undo, "placing gives one undo step");
|
||||
scene->undoStack().push(undo);
|
||||
QRectF b = block(texts);
|
||||
check(same(b.right(), body(element).left() - SymbolTextPlacement::gap), "left: the block ends 3 px before the drawing");
|
||||
check(same(b.center().y(), body(element).center().y()), "left: halfway up the drawing");
|
||||
bool right_aligned = true, keeps_order = true;
|
||||
for (int i = 0; i < texts.size(); ++i) {
|
||||
right_aligned &= same(texts[i]->sceneBoundingRect().right(), b.right());
|
||||
right_aligned &= bool(texts[i]->alignment() & Qt::AlignRight);
|
||||
if (i) keeps_order &= same(texts[i]->sceneBoundingRect().top() - texts[i-1]->sceneBoundingRect().top(), 12);
|
||||
}
|
||||
check(right_aligned, "left: the lines are aligned right, on the screen and as their alignment");
|
||||
check(keeps_order, "the lines keep their order and their spacing");
|
||||
check(same(b.height(), before.height()), "the block keeps its height");
|
||||
|
||||
scene->undoStack().undo();
|
||||
bool back = true;
|
||||
for (int i = 0; i < texts.size(); ++i) back &= texts[i]->pos() == start[i] && !(texts[i]->alignment() & Qt::AlignRight);
|
||||
check(back, "one undo puts every text and its alignment back");
|
||||
scene->undoStack().redo();
|
||||
|
||||
// Right of it: aligned left. Above it: centred.
|
||||
scene->undoStack().push(SymbolTextPlacement::place({element}, Qt::AlignRight | Qt::AlignVCenter));
|
||||
b = block(texts);
|
||||
check(same(b.left(), body(element).right() + SymbolTextPlacement::gap), "right: the block starts 3 px after the drawing");
|
||||
check(same(texts[1]->sceneBoundingRect().left(), b.left()) && (texts[1]->alignment() & Qt::AlignLeft),
|
||||
"right: the lines are aligned left");
|
||||
scene->undoStack().push(SymbolTextPlacement::place({element}, Qt::AlignHCenter | Qt::AlignTop));
|
||||
b = block(texts);
|
||||
check(same(b.bottom(), body(element).top() - SymbolTextPlacement::gap), "above: the block ends 3 px above the drawing");
|
||||
check(same(texts[0]->sceneBoundingRect().center().x(), body(element).center().x()), "above: the lines are centred on it");
|
||||
|
||||
// A turned symbol: left in the folio is still left.
|
||||
element->setRotation(90);
|
||||
scene->undoStack().push(SymbolTextPlacement::place({element}, Qt::AlignLeft | Qt::AlignVCenter));
|
||||
b = block(texts);
|
||||
check(same(b.right(), body(element).left() - SymbolTextPlacement::gap), "turned symbol: the block is left of it in the folio");
|
||||
element->setRotation(0);
|
||||
|
||||
// A group of texts is one line of the block, aligned like it.
|
||||
ElementTextItemGroup *group = element->addTextGroup(QStringLiteral("g"));
|
||||
element->addTextToGroup(texts[1], group);
|
||||
element->addTextToGroup(texts[2], group);
|
||||
scene->undoStack().push(SymbolTextPlacement::place({element}, Qt::AlignLeft | Qt::AlignVCenter));
|
||||
QRectF with_group = texts[0]->sceneBoundingRect() | group->sceneBoundingRect();
|
||||
check(same(with_group.right(), body(element).left() - SymbolTextPlacement::gap)
|
||||
&& same(group->sceneBoundingRect().right(), with_group.right()),
|
||||
"a group is placed as one line of the block");
|
||||
check(group->alignment() == Qt::AlignRight, "and is aligned right too");
|
||||
|
||||
// A symbol with no texts: nothing to undo.
|
||||
Element *other = nullptr;
|
||||
for (Element *e : scene->elements()) if (e != element) other = e;
|
||||
if (other) {
|
||||
for (DynamicElementTextItem *t : other->dynamicTextItems()) t->setVisible(false);
|
||||
check(SymbolTextPlacement::place({other}, Qt::AlignLeft) == nullptr, "a symbol without texts gives no undo step");
|
||||
}
|
||||
std::puts("PASS");
|
||||
}
|
||||
catch (const std::exception &error)
|
||||
{
|
||||
std::printf("FAIL: %s\n", error.what());
|
||||
std::fflush(stdout);
|
||||
return 1;
|
||||
}
|
||||
std::fflush(stdout);
|
||||
return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user