Keep the width of free texts in the project database

The independent_text table had the text and rotation of a free text but
not its width, and a width change did not update the row at all -- not
even its width and height, which follow the wrapped text. Add a
text_width column (NULL for the automatic width) and update the row on
textWidthChanged.

Test: tst_scriptfreetextwidth reads the table with qet.query() (fails
before).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Beat Hangartner
2026-10-06 09:50:08 +02:00
parent 1e3c618495
commit 315de47a12
3 changed files with 119 additions and 3 deletions
+8 -3
View File
@@ -1248,6 +1248,8 @@ void projectDataBase::addDrawingItem(QGraphicsItem *item)
}
else if (auto text = qobject_cast<IndependentTextItem *>(object)) {
connect(text, &IndependentTextItem::uuidChanged, this, &projectDataBase::drawingItemChanged, unique);
//Also changes the height of the text, kept in the row
connect(text, &IndependentTextItem::textWidthChanged, this, &projectDataBase::drawingItemChanged, unique);
//Sent by the document, not the item: drawingItemChanged() walks
//back up to the item. It is the one signal that catches every way
//the text changes -- typing, undo, a script's setTextContent().
@@ -1424,6 +1426,9 @@ bool projectDataBase::writeDrawingItem(QObject *object)
query = &m_insert_independent_text_query;
query->bindValue(QStringLiteral(":text"), text->toPlainText());
query->bindValue(QStringLiteral(":rotation"), text->rotation());
//NULL for the automatic width
query->bindValue(QStringLiteral(":text_width"), text->textWidth() > 0
? QVariant(text->textWidth()) : QVariant());
}
else if (auto image = qobject_cast<DiagramImageItem *>(object))
{
@@ -1667,7 +1672,7 @@ bool projectDataBase::createDataBase()
"type VARCHAR(20), color VARCHAR(20), fill VARCHAR(20), "
"FOREIGN KEY (diagram_uuid) REFERENCES diagram (uuid))",
QStringLiteral("CREATE TABLE independent_text (") + drawing_columns +
"text TEXT, rotation REAL, "
"text TEXT, rotation REAL, text_width REAL, "
"FOREIGN KEY (diagram_uuid) REFERENCES diagram (uuid))",
QStringLiteral("CREATE TABLE image (") + drawing_columns +
"pixel_width INTEGER, pixel_height INTEGER, "
@@ -2123,8 +2128,8 @@ void projectDataBase::prepareQuery()
", :type, :color, :fill)");
m_insert_independent_text_query = QSqlQuery(m_data_base);
m_insert_independent_text_query.prepare("INSERT OR REPLACE INTO independent_text (" + drawing_columns +
", text, rotation) VALUES (" + drawing_values +
", :text, :rotation)");
", text, rotation, text_width) VALUES (" + drawing_values +
", :text, :rotation, :text_width)");
m_insert_image_query = QSqlQuery(m_data_base);
m_insert_image_query.prepare("INSERT OR REPLACE INTO image (" + drawing_columns +
", pixel_width, pixel_height) VALUES (" + drawing_values +
+11
View File
@@ -543,6 +543,17 @@ if(QET_HAS_SCRIPTING)
target_compile_definitions(tst_scriptlayoutreads PRIVATE
"QET_TEST_BINARY_PATH=\"$<TARGET_FILE:qelectrotech>\"")
# The project database keeps the width of a free text (independent_text),
# read with qet.query() on fixtures/free_text_width.qet.
add_executable(
tst_scriptfreetextwidth
tst_scriptfreetextwidth.cpp)
add_test(NAME tst_scriptfreetextwidth COMMAND tst_scriptfreetextwidth)
add_dependencies(tst_scriptfreetextwidth qelectrotech)
target_link_libraries(tst_scriptfreetextwidth PRIVATE Qt::Test)
target_compile_definitions(tst_scriptfreetextwidth PRIVATE
"QET_TEST_BINARY_PATH=\"$<TARGET_FILE:qelectrotech>\"")
# QET_SETTINGS_DIR moves the settings into an INI file there (#1178): a
# script places a symbol only the folder's settings file can resolve.
add_executable(
+100
View File
@@ -0,0 +1,100 @@
/*
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 <QtTest>
#include <QJsonArray>
#include <QJsonDocument>
#include <QJsonObject>
#include <QProcess>
#include <QTemporaryDir>
/**
The project database keeps the width of a free text (text_width in the
independent_text table, NULL for the automatic width), read here with
qet.query() from a script run on fixtures/free_text_width.qet.
*/
class tst_scriptfreetextwidth : public QObject
{
Q_OBJECT
QTemporaryDir m_dir;
/// The rows of the independent_text table, by the first word of the text.
QHash<QString, QJsonObject> rows()
{
const QString path = m_dir.filePath(QStringLiteral("probe.js"));
const QString home = m_dir.filePath(QStringLiteral("home"));
QDir().mkpath(home);
QFile f(path);
if (!f.open(QIODevice::WriteOnly)) return {};
f.write("qet.log('PROBE ' + JSON.stringify(qet.query("
"'SELECT text, text_width, width, height FROM independent_text')));\n");
f.close();
QProcessEnvironment env = QProcessEnvironment::systemEnvironment();
env.insert(QStringLiteral("QT_QPA_PLATFORM"), QStringLiteral("offscreen"));
env.insert(QStringLiteral("QET_ENABLE_SCRIPTING"), QStringLiteral("1"));
env.insert(QStringLiteral("HOME"), home);
env.insert(QStringLiteral("XDG_CONFIG_HOME"), home + QStringLiteral("/config"));
env.insert(QStringLiteral("XDG_DATA_HOME"), home + QStringLiteral("/data"));
QProcess proc;
proc.setProcessEnvironment(env);
proc.start(QStringLiteral(QET_TEST_BINARY_PATH),
{QStringLiteral("--run"), path, QFINDTESTDATA("fixtures/free_text_width.qet")});
if (!proc.waitForFinished(60000)) return {};
const QString out = QString::fromUtf8(proc.readAllStandardOutput() + proc.readAllStandardError());
const QString mark = QStringLiteral("PROBE ");
QHash<QString, QJsonObject> result;
for (const QString &line : out.split(QLatin1Char('\n'))) {
const int i = line.indexOf(mark);
if (i < 0) continue;
const QJsonArray array = QJsonDocument::fromJson(line.mid(i + mark.size()).toUtf8()).array();
for (const QJsonValue &row : array)
result.insert(row.toObject().value(QStringLiteral("text")).toString().section(QLatin1Char(' '), 0, 0),
row.toObject());
}
return result;
}
private slots:
void initTestCase()
{
QVERIFY(m_dir.isValid());
QVERIFY(QFile::exists(QStringLiteral(QET_TEST_BINARY_PATH)));
QVERIFY(!QFINDTESTDATA("fixtures/free_text_width.qet").isEmpty());
}
void widthIsInTheDatabase()
{
const QHash<QString, QJsonObject> r = rows();
QCOMPARE(r.size(), 3);
QCOMPARE(r.value(QStringLiteral("FreeAlpha")).value(QStringLiteral("text_width")).toDouble(), 70.0);
QCOMPARE(r.value(QStringLiteral("CentAlpha")).value(QStringLiteral("text_width")).toDouble(), 120.0);
//The automatic width is NULL
const QJsonValue open = r.value(QStringLiteral("OpenAlpha")).value(QStringLiteral("text_width"));
QVERIFY2(open.isNull() || open.toString().isEmpty(), qPrintable(open.toVariant().toString()));
//The box of the row is the wrapped one: taller than one line
QVERIFY(r.value(QStringLiteral("FreeAlpha")).value(QStringLiteral("height")).toDouble()
> 2 * r.value(QStringLiteral("OpenAlpha")).value(QStringLiteral("height")).toDouble());
}
};
QTEST_APPLESS_MAIN(tst_scriptfreetextwidth)
#include "tst_scriptfreetextwidth.moc"