Merge branch 'master' into feat/1070-item-groups

This commit is contained in:
Laurent Trinques
2026-09-27 20:15:27 +02:00
committed by GitHub
25 changed files with 799 additions and 400 deletions
+3
View File
@@ -192,6 +192,8 @@ set(QET_SRC_FILES
${QET_DIR}/sources/configdialog.h
${QET_DIR}/sources/createdxf.cpp
${QET_DIR}/sources/createdxf.h
${QET_DIR}/sources/dxfexport.cpp
${QET_DIR}/sources/dxfexport.h
${QET_DIR}/sources/dxfpaintdevice.cpp
${QET_DIR}/sources/dxfpaintdevice.h
${QET_DIR}/sources/diagramcommands.cpp
@@ -327,6 +329,7 @@ set(QET_SRC_FILES
${QET_DIR}/sources/autoNum/ui/selectautonumw.cpp
${QET_DIR}/sources/autoNum/ui/selectautonumw.h
${QET_DIR}/sources/dataBase/legacyelementtypes.h
${QET_DIR}/sources/dataBase/projectdatabase.cpp
${QET_DIR}/sources/dataBase/projectdatabase.h
${QET_DIR}/sources/dataBase/sqlreadonly.cpp
+1 -1
View File
@@ -33,7 +33,7 @@ here read the model.
| `qet_diff` | **what an edit actually changed** — element moves, adds, removes, relabels; conductor changes; and folio fields, texts, shapes, images, symbol text fields and terminal strips |
| `qet_scan` | sweep a directory of projects, counting nodes carrying an attribute |
| `qet_element_info` | a `.elmt`: translated names, terminals, info fields, part counts |
| `qet_export` | run a headless export (pdf, png, svg, bom, cables, wires, wiring, nets, links, info) |
| `qet_export` | run a headless export (pdf, png, svg, dxf, bom, cables, wires, wiring, nets, links, info) |
| `qet_edit` | **change a project** — place, move, rotate, label, wire, number, cross-reference, add text, shapes and images, restyle a symbol's text fields, delete; then diff the result |
| `qet_element_build` | **author a `.elmt`** — draw a new symbol, with terminals to wire it by |
| `qet_project_new` | **start from nothing** — an empty project with a title and folios |
+2 -1
View File
@@ -69,6 +69,7 @@ EXPORT_FORMATS = {
"pdf": "--export-pdf",
"png": "--export-png",
"svg": "--export-svg",
"dxf": "--export-dxf",
"bom": "--export-bom",
"cables": "--export-cables",
"wires": "--export-wires",
@@ -2186,7 +2187,7 @@ TOOLS = [
},
{
"name": "qet_export",
"description": "Run a QElectroTech export headlessly (pdf, png, svg, bom, "
"description": "Run a QElectroTech export headlessly (pdf, png, svg, dxf, bom, "
"cables, wires, wiring, nets, links, info). Launches the "
"binary in an isolated sandbox so it cannot be captured by, "
"or capture, a running QElectroTech.",
@@ -404,10 +404,16 @@ void ElementPickerPopup::setCommands(const QStringList &ids)
button->setIcon(action->icon());
}
const QKeySequence key = action->shortcut();
button->setToolTip(key.isEmpty()
? text
: QStringLiteral("%1 (%2)").arg(
text, key.toString(QKeySequence::NativeText)));
QString tip = key.isEmpty()
? text
: QStringLiteral("%1 (%2)").arg(
text, key.toString(QKeySequence::NativeText));
//The status tip says what the command does, which a short
//name such as "Orienter les textes" does not
if (!action->statusTip().isEmpty()) {
tip += QLatin1Char('\n') + action->statusTip();
}
button->setToolTip(tip);
button->setEnabled(action->isEnabled());
button->setFocusPolicy(Qt::NoFocus);
connect(button, &QToolButton::clicked, this, [this, action]() {
@@ -1103,7 +1103,32 @@ void ElementsCollectionWidget::search()
return;
}
showFlatResults(rankedSearch(text, m_showed_index));
QSettings settings;
if (settings.value(QStringLiteral("elementscollection/search-flat-list"),
true).toBool())
{
showFlatResults(rankedSearch(text, m_showed_index));
return;
}
//Tree search: hide what does not match, expand what does.
clearFlatResults();
hideCollection(true);
const QStringList text_list = text.split("+", Qt::SkipEmptyParts);
QModelIndexList match_index;
for (const QString &txt : text_list) {
match_index << m_model->match(m_showed_index.isValid()
? m_model->index(0,0,m_showed_index)
: m_model->index(0,0),
Qt::UserRole+1,
QVariant(txt),
-1,
Qt::MatchContains
| Qt::MatchRecursive);
}
for (const QModelIndex &index : std::as_const(match_index))
showAndExpandItem(index);
}
/**
+57
View File
@@ -24,8 +24,11 @@
#include "dataBase/projectdatabase.h"
#include "diagram.h"
#include "diagramcontext.h"
#include "dxfexport.h"
#include "exportproperties.h"
#include "pdf_links.h"
#include "qetgraphicsitem/conductor.h"
#include "qetgraphicsitem/diagramimageitem.h"
#include "qetgraphicsitem/element.h"
#include "qetgraphicsitem/terminal.h"
#include "qetproject.h"
@@ -69,6 +72,7 @@ const QHash<QString, QString> &exportFlags()
{"--export-pdf", "pdf"},
{"--export-png", "png"},
{"--export-svg", "svg"},
{"--export-dxf", "dxf"},
{"--export-cables", "cables"},
{"--export-wires", "wires"},
{"--export-bom", "bom"},
@@ -279,6 +283,57 @@ int exportImages(QETProject &project, const QString &format,
return 0;
}
/// One DXF file per diagram, written by the same code as the export dialog
/// (DxfExport) with the dialog's default options -- the export settings of
/// the preferences -- so both give the same file.
int exportDxf(QETProject &project, const QString &out_dir, bool showTerminals)
{
const QList<Diagram *> diagrams = project.diagrams();
if (diagrams.isEmpty()) {
err << "No diagrams to export.\n";
return 1;
}
QDir().mkpath(out_dir);
ExportProperties properties = ExportProperties::defaultExportProperties();
properties.format = QStringLiteral("DXF");
if (showTerminals)
properties.draw_terminals = true;
int index = 0;
bool has_images = false;
for (Diagram *diagram : diagrams) {
++index;
const QString path = QDir(out_dir).filePath(
diagramStem(diagram, index) + ".dxf");
// Createdxf answers a file it cannot open with a message box and
// exit(0), which headless means no message and a success code.
QFile probe(path);
if (!probe.open(QIODevice::WriteOnly)) {
err << "Cannot open '" << path << "' for writing.\n";
return 1;
}
probe.close();
const QSize size = DxfExport::folioSize(diagram, properties);
DxfExport::write(diagram, size.width(), size.height(), path, properties);
for (QGraphicsItem *item : diagram->items()) {
if (qgraphicsitem_cast<DiagramImageItem *>(item)) {
has_images = true;
break;
}
}
out << " " << path << "\n";
}
if (has_images)
err << "Note: DXF has no pictures in this format; "
"each picture is exported as an outline box.\n";
out << "Exported " << diagrams.size() << " diagram(s) -> " << out_dir << "\n";
return 0;
}
int exportCsv(QETProject &project, const QString &format, const QString &output)
{
QString csv;
@@ -890,6 +945,8 @@ int run(const QStringList &args)
}
if (format == "pdf")
return exportPdf(project, output, showTerminals);
if (format == "dxf")
return exportDxf(project, output, showTerminals);
if (format == "cables" || format == "wires")
return exportCsv(project, format, output);
if (format == "bom")
+6 -2
View File
@@ -45,6 +45,7 @@ namespace CLIExport {
qelectrotech --export-pdf <project.qet> <output.pdf> [--show-terminals]
qelectrotech --export-png <project.qet> <output_dir> [--show-terminals]
qelectrotech --export-svg <project.qet> <output_dir> [--show-terminals]
qelectrotech --export-dxf <project.qet> <output_dir> [--show-terminals]
qelectrotech --export-cables <project.qet> <output.csv>
qelectrotech --export-wires <project.qet> <output.csv>
qelectrotech --export-bom <project.qet> <output.csv>
@@ -57,11 +58,14 @@ namespace CLIExport {
qelectrotech --set-titleblock <project.qet> <output.qet> key=value...
PDF: one multi-page document (one diagram per page).
PNG/SVG: one file per diagram, named <output_dir>/<NN>_<title>.<ext>.
PNG/SVG/DXF: one file per diagram, named <output_dir>/<NN>_<title>.<ext>.
DXF: the same file the export dialog writes with its default options
(the export settings of the preferences).
--show-terminals: also paint terminal markers (red stroke + blue
docking dot) and terminal names, as the interactive editor
does; off by default, matching the GUI export dialog's
default. Has no effect on the non-image export modes.
default. For DXF, draws the terminal markers. Has no effect on
the list export modes.
cables: wiring list (one row per conductor) as CSV.
wires: list of distinct wire numbers as CSV.
bom: bill of materials (one row per element) as CSV.
+2 -2
View File
@@ -20,7 +20,7 @@
#include <QTextStream>
#include <QMessageBox>
#include <QString>
#include "exportdialog.h"
#include "dxfexport.h"
const double Createdxf::sheetWidth = 4000;
@@ -583,7 +583,7 @@ void Createdxf::drawArcEllipse(
arc_endAngle = temp;
}
QPointF transformed_point = ExportDialog::rotation_transformed(
QPointF transformed_point = DxfExport::rotation_transformed(
center_x,
center_y,
hotspot_x,
+69
View File
@@ -0,0 +1,69 @@
/*
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 LEGACYELEMENTTYPES_H
#define LEGACYELEMENTTYPES_H
#include <QRegularExpression>
#include <QString>
/**
Element type names in queries saved by versions before June 2022.
The project database then held Element::linkTypeToString() ("Simple",
"Terminale", ...); since it holds ElementData::typeToString()
("simple", "terminal", ...). SQLite compares text case-sensitively, so
a nomenclature table saved with element_type = 'Simple' silently lost
every simple element, and its continuation tables were deleted as
empty.
*/
namespace LegacyElementTypes
{
/**
@return query with every element_type = '<old name>' comparison
rewritten to the current name. Anything else is left as it is,
so a query saved by a current version comes back unchanged.
*/
inline QString upgradeQuery(const QString &query)
{
static const QRegularExpression re(QStringLiteral(
"element_type\\s*=\\s*'(Simple|NextReport|PreviousReport|Master"
"|Slave|Terminale|Thumbnail)'"));
QString upgraded = query;
auto it = re.globalMatch(query);
int shift = 0;
while (it.hasNext())
{
const auto match = it.next();
const QString old_name = match.captured(1);
QString new_name;
if (old_name == QLatin1String("Simple")) new_name = QStringLiteral("simple");
else if (old_name == QLatin1String("NextReport")) new_name = QStringLiteral("next_report");
else if (old_name == QLatin1String("PreviousReport")) new_name = QStringLiteral("previous_report");
else if (old_name == QLatin1String("Master")) new_name = QStringLiteral("master");
else if (old_name == QLatin1String("Slave")) new_name = QStringLiteral("slave");
else if (old_name == QLatin1String("Terminale")) new_name = QStringLiteral("terminal");
else new_name = QStringLiteral("thumbnail");
upgraded.replace(match.capturedStart(1) + shift, old_name.size(), new_name);
shift += new_name.size() - old_name.size();
}
return upgraded;
}
}
#endif // LEGACYELEMENTTYPES_H
+4 -1
View File
@@ -20,6 +20,7 @@
#include "../../properties/elementdata.h"
#include "../../qetapp.h"
#include "../../qetinformation.h"
#include "../legacyelementtypes.h"
#include "../projectdatabase.h"
#include "ui_elementquerywidget.h"
@@ -674,7 +675,9 @@ void ElementQueryWidget::on_m_load_pb_clicked()
auto value_object = value.toObject();
if (value_object.value("query").isString()) {
setQuery(value_object.value("query").toString());
//A query saved before June 2022 names element types the old way
setQuery(LegacyElementTypes::upgradeQuery(
value_object.value("query").toString()));
}
}
+6 -1
View File
@@ -23,6 +23,7 @@
#include <QHBoxLayout>
#include <QMouseEvent>
#include <QSettings>
#include <QStringBuilder>
#include <QToolButton>
namespace {
@@ -92,7 +93,11 @@ void DiagramContextToolbar::showAt(const QPoint &viewport_pos,
} else {
button->setIcon(action->icon());
}
button->setToolTip(text);
//The status tip says what the command does, which a short
//name such as "Orienter les textes" does not
button->setToolTip(action->statusTip().isEmpty()
? text
: text % QLatin1Char('\n') % action->statusTip());
button->setEnabled(action->isEnabled());
connect(button, &QToolButton::clicked, action, &QAction::trigger);
m_layout->addWidget(button);
+438
View File
@@ -0,0 +1,438 @@
/*
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 "dxfexport.h"
#include "conductorsegment.h"
#include "createdxf.h"
#include "diagram.h"
#include "dxfpaintdevice.h"
#include "exportproperties.h"
#include "factory/elementpicturefactory.h"
#include "qetgraphicsitem/ViewItem/qetgraphicstableitem.h"
#include "qetgraphicsitem/conductor.h"
#include "qetgraphicsitem/conductortextitem.h"
#include "qetgraphicsitem/crossrefitem.h"
#include "qetgraphicsitem/diagramimageitem.h"
#include "qetgraphicsitem/diagramtextitem.h"
#include "qetgraphicsitem/dynamicelementtextitem.h"
#include "qetgraphicsitem/element.h"
#include "qetgraphicsitem/independenttextitem.h"
#include "qetgraphicsitem/qetshapeitem.h"
#include "qetgraphicsitem/terminal.h"
#include <QGraphicsSimpleTextItem>
#include <QSet>
#include <cmath>
#include <utility>
/**
@brief DxfExport::folioSize
@return the size of @a diagram as exported with @a properties: its border
and title block, or just its items, following properties.exported_area.
The export dialog offers this size by default, and --export-dxf uses it.
*/
QSize DxfExport::folioSize(Diagram *diagram, const ExportProperties &properties)
{
const bool state_useBorder = diagram -> useBorder();
diagram -> setUseBorder(properties.exported_area == QET::BorderArea);
const QSize size = diagram -> imageSize();
diagram -> setUseBorder(state_useBorder);
return size;
}
/**
@brief DxfExport::write
Export @a diagram as a DXF file. Moved here unchanged from
ExportDialog::generateDxf(), except that the options come from
@a properties instead of the dialog, so the command line can use it.
The diagram's own display options are applied for the export and put
back afterwards, as the dialog always did.
@param diagram : folio to export
@param width : width of the export, as offered by folioSize()
@param height : height of the export
@param path : file to write
@param properties : export options
*/
void DxfExport::write(Diagram *diagram, int width, int height,
const QString &path, const ExportProperties &properties)
{
//Non-const: BorderTitleBlock::drawDxf() takes a QString &.
QString file_path = path;
const ExportProperties previous = diagram -> applyProperties(properties);
width -= 2*Diagram::margin;
height -= 2*Diagram::margin;
Createdxf::xScale = Createdxf::sheetWidth / double(width);
Createdxf::yScale = Createdxf::sheetHeight / double(height);
Createdxf::dxfBegin(file_path);
//Add project elements (lines, rectangles, circles, texts) to dxf file
if (properties.draw_border) {
QRectF rect(Diagram::margin,Diagram::margin,width,height);
Createdxf::drawRectangle(file_path,rect,0);
}
diagram -> border_and_titleblock.drawDxf(file_path, 0);
// Build the lists of elements.
QList<Element *> list_elements;
QList<Conductor *> list_conductors;
QList<DiagramTextItem *> list_texts;
QList<DiagramImageItem *> list_images;
//Slave cross-reference labels. They hang off a DynamicElementTextItem
//as plain QGraphicsTextItem children, so neither cast below picks them
//up and they were missing from the DXF entirely.
QList<QGraphicsTextItem *> list_xref_texts;
//Master-side cross-reference item (the table/cross drawn next to a
//report/master element). It paints itself with hand-written
//QPainter code across three modes (drawAsCross/drawAsContacts/
//drawAsPlcTable), so instead of hand-porting each one it's replayed
//through DxfPaintEngine, which reuses paint() unmodified.
QList<CrossRefItem *> list_master_xrefs;
QList<QLineF *> list_lines;
QList<QRectF *> list_rectangles;
//QList<QRectF *> list_ellipses;
QList <QetShapeItem *> list_shapes;
QList <QetGraphicsTableItem *> list_tables;
// QList <Terminal *> list_terminals;
// Determine les elements a "XMLiser"
//In stacking order (z, then insertion order, which a load makes the
//file's order), not items() order: that follows memory addresses, so
//the same folio came out in a different order on every run (FINDINGS
//F050), as saving did before bugtracker #343. A rect query gives
//stacking order even with NoIndex; anything it misses keeps its
//items() position after the rest.
QList<QGraphicsItem *> stacked_items = diagram -> items(
QRectF(-1e9, -1e9, 2e9, 2e9), Qt::IntersectsItemBoundingRect,
Qt::AscendingOrder);
{
const QSet<QGraphicsItem *> ranked(stacked_items.cbegin(), stacked_items.cend());
for (QGraphicsItem *qgi : diagram -> items()) {
if (!ranked.contains(qgi)) {
stacked_items << qgi;
}
}
}
for (QGraphicsItem *qgi : std::as_const(stacked_items)) {
if (Element *elmt = qgraphicsitem_cast<Element *>(qgi)) {
list_elements << elmt;
} else if (Conductor *f = qgraphicsitem_cast<Conductor *>(qgi)) {
list_conductors << f;
} else if (IndependentTextItem *iti = qgraphicsitem_cast<IndependentTextItem *>(qgi)) {
list_texts << iti;
} else if (DiagramImageItem *dii = qgraphicsitem_cast<DiagramImageItem *>(qgi)) {
list_images << dii;
} else if (QetShapeItem *dii = qgraphicsitem_cast<QetShapeItem *>(qgi)) {
list_shapes << dii;
} else if (DynamicElementTextItem *deti = qgraphicsitem_cast<DynamicElementTextItem *>(qgi)) {
list_texts << deti;
if (QGraphicsTextItem *xref = deti->slaveXrefItem()) {
list_xref_texts << xref;
}
} else if (QetGraphicsTableItem *gti = qgraphicsitem_cast<QetGraphicsTableItem *>(qgi)) {
list_tables << gti;
} else if (CrossRefItem *xref = qgraphicsitem_cast<CrossRefItem *>(qgi)) {
list_master_xrefs << xref;
}
}
// Draw shapes
foreach (QetShapeItem *qsi, list_shapes) qsi->toDXF(file_path, qsi->pen());
// Draw tables
foreach (QetGraphicsTableItem *gti, list_tables) {
gti->toDXF(file_path);
}
//Draw elements
foreach(Element *elmt, list_elements)
{
double rotation_angle = elmt -> orientation() * 90;
qreal elem_pos_x = elmt -> pos().x();
qreal elem_pos_y = elmt -> pos().y();// - (diagram -> margin / 2);
ElementPictureFactory::primitives primitives = ElementPictureFactory::instance()->getPrimitives(elmt->location());
for(QGraphicsSimpleTextItem *text : primitives.m_texts)
{
qreal fontSize = text->font().pointSizeF();
if (fontSize < 0)
fontSize = text->font().pixelSize();
qreal x = elem_pos_x + text->pos().x();
qreal y = elem_pos_y + text->pos().y();
qreal angle = text -> rotation() + rotation_angle;
qreal angler = angle * M_PI/180;
int xdir = -sin(angler);
int ydir = -cos(angler);
QPointF transformed_point = rotation_transformed(x, y, elem_pos_x, elem_pos_y, -rotation_angle);
x = transformed_point.x() - ydir * fontSize * 0.5;
y = transformed_point.y() - xdir * fontSize * 0.5;
QStringList lines = text->text().split('\n');
qreal offset = fontSize * 1.6;
for (QString line : lines)
{
if (line.size() > 0 && line != "_" ) {
Createdxf::drawText(file_path, line, QPointF(x, y), fontSize, 360 - angle, 0, 0.72);
}
x += offset * xdir;
y -= offset * ydir;
}
}
for (QLineF line : primitives.m_lines)
{
QTransform t = QTransform().translate(elem_pos_x,elem_pos_y).rotate(rotation_angle);
QLineF l = t.map(line);
Createdxf::drawLine(file_path, l, 0);
}
for (QRectF rect : primitives.m_rectangles)
{
QTransform t = QTransform().translate(elem_pos_x,elem_pos_y).rotate(rotation_angle);
QRectF r = t.mapRect(rect);
Createdxf::drawRectangle(file_path,r,0);
}
for (QRectF circle_rect : primitives.m_circles)
{
QTransform t = QTransform().translate(elem_pos_x,elem_pos_y).rotate(rotation_angle);
QPointF c = t.map(QPointF(circle_rect.center().x(),circle_rect.center().y()));
Createdxf::drawCircle(file_path,c,circle_rect.width()/2,0);
}
for (QVector<QPointF> polygon : primitives.m_polygons)
{
if (polygon.size() == 0)
continue;
QTransform t = QTransform().translate(elem_pos_x,elem_pos_y).rotate(rotation_angle);
QPolygonF poly = t.map(polygon);
if(poly.isClosed())
Createdxf::drawPolygon(file_path,poly,0);
else
Createdxf::drawPolyline(file_path,poly,0);
}
// Draw arcs and ellipses
for (QVector<qreal> arc : primitives.m_arcs)
{
if (arc.size() == 0)
continue;
qreal x = (elem_pos_x + arc.at(0));
qreal y = (elem_pos_y + arc.at(1));
qreal w = arc.at(2);
qreal h = arc.at(3);
qreal startAngle = arc.at(4);
qreal spanAngle = arc .at(5);
QRectF r(x,y,w,h);
QPointF hotspot(elem_pos_x,elem_pos_y);
Createdxf::drawArcEllipse(file_path, r, startAngle, spanAngle, hotspot, rotation_angle, 0);
}
if (properties.draw_terminals) {
// Draw terminals
QList<Terminal *> list_terminals = elmt->terminals();
QColor col("red");
QTransform t = QTransform().translate(elem_pos_x,elem_pos_y).rotate(rotation_angle);
foreach(Terminal *tp, list_terminals) {
QPointF c = t.map(QPointF(tp->dock_elmt_.x(),tp->dock_elmt_.y()));
Createdxf::drawCircle(file_path,c,3.0,Createdxf::dxfColor(col));
}
}
}
//Draw conductors
foreach(Conductor *cond, list_conductors) {
QPolygonF poly;
bool firstseg = true;
foreach(ConductorSegment *segment, cond -> segmentsList()) {
//Createdxf::drawLine(file_path,QLineF(cond->pos()+segment->firstPoint(),cond->pos()+segment->secondPoint()),0);
if(firstseg){
poly << cond->pos()+segment->firstPoint();
firstseg = false;
}
poly << cond->pos()+segment->secondPoint();
}
Createdxf::drawPolyline(file_path,poly,0);
//Draw conductor text item
ConductorTextItem *textItem = cond -> textItem();
if (textItem) {
qreal fontSize = textItem -> font().pointSizeF();
if (fontSize < 0)
fontSize = textItem -> font().pixelSize();
qreal angle = textItem -> rotation();
qreal angler = angle * M_PI/180;
int xdir = -sin(angler);
int ydir = -cos(angler);
qreal x = (cond->pos().x() + textItem -> pos().x())
+ xdir * fontSize * 1.8
- ydir * fontSize;
qreal y = (cond->pos().y() + textItem -> pos().y())
- ydir * fontSize * 1.8
- xdir * fontSize * 0.9;
QStringList lines = textItem->toPlainText().split('\n');
qreal offset = fontSize * 1.6;
foreach (QString line, lines) {
if (line.size() > 0 && line != "_" )
Createdxf::drawText(file_path, line, QPointF(x, y), fontSize, 360-angle, 0, 0.72 );
x += offset * xdir;
y -= offset * ydir;
}
}
// Draw the junctions
QList<QPointF> junctions_list = cond->junctions();
if (!junctions_list.isEmpty()) {
foreach(QPointF point, junctions_list) {
Createdxf::drawEllipse(file_path,QRectF(cond->pos().x() + point.x() - 1.5, cond->pos().y() + point.y() - 1.5, 3.0, 3.0),0);
}
}
}
//Draw text items
foreach(DiagramTextItem *dti, list_texts) {
qreal fontSize = dti -> font().pointSizeF();
if (fontSize < 0)
fontSize = dti -> font().pixelSize();
qreal angle = dti -> rotation();
QGraphicsItem *parent = dti->parentItem();
while (parent) {
angle += parent->rotation();
parent = parent->parentItem();
}
qreal angler = angle * M_PI/180;
int xdir = -sin(angler);
int ydir = -cos(angler);
qreal x = (dti->scenePos().x())
+ xdir * fontSize * 1.8
- ydir * fontSize;
qreal y = dti->scenePos().y()
- ydir * fontSize * 1.8
- xdir * fontSize * 0.9;
QStringList lines = dti -> toPlainText().split('\n');
qreal offset = fontSize * 1.6;
foreach (QString line, lines) {
if (line.size() > 0 && line != "_" )
Createdxf::drawText(file_path, line, QPointF(x, y), fontSize, 360-angle, Createdxf::dxfColor(dti->color()), 0.72 );
x += offset * xdir;
y -= offset * ydir;
}
}
//Draw the slave cross-reference labels
for (QGraphicsTextItem *xref : std::as_const(list_xref_texts))
{
qreal fontSize = xref->font().pointSizeF();
if (fontSize < 0)
fontSize = xref->font().pixelSize();
qreal angle = xref->rotation();
QGraphicsItem *parent = xref->parentItem();
while (parent) {
angle += parent->rotation();
parent = parent->parentItem();
}
qreal angler = angle * M_PI/180;
int xdir = -sin(angler);
int ydir = -cos(angler);
qreal x = xref->scenePos().x()
+ xdir * fontSize * 1.8
- ydir * fontSize;
qreal y = xref->scenePos().y()
- ydir * fontSize * 1.8
- xdir * fontSize * 0.9;
const QStringList lines = xref->toPlainText().split('\n');
const qreal offset = fontSize * 1.6;
for (const QString &line : lines) {
if (line.size() > 0 && line != QLatin1String("_")) {
Createdxf::drawText(file_path, line, QPointF(x, y), fontSize,
360-angle, Createdxf::dxfColor(xref->defaultTextColor()), 0.72);
}
x += offset * xdir;
y -= offset * ydir;
}
}
//Draw the master-side cross-reference items (table/cross), replaying
//their existing paint() unmodified through DxfPaintEngine instead of
//hand-porting drawAsCross()/drawAsContacts()/drawAsPlcTable().
for (CrossRefItem *xref : std::as_const(list_master_xrefs))
{
DxfPaintDevice dxf_device(file_path);
QPainter painter(&dxf_device);
painter.setWorldTransform(xref->sceneTransform());
xref->paintForExport(&painter);
painter.end();
}
//Draw images -- collected above (list_images) but never actually
//drawn until now, an existing gap this reuses the same paint()
//-replay approach to fix: DiagramImageItem::paint() has no
//viewport-dependent logic (unlike CrossRefItem, which needs its own
//paintForExport() for that reason), so it's called directly with a
//default QStyleOptionGraphicsItem rather than needing an export-
//specific variant of its own. DxfPaintEngine::drawPixmap() is what
//actually turns the drawPixmap() call inside paint() into a
//placeholder outline, since this DXF dialect has no raster image
//entity to draw instead.
for (DiagramImageItem *image : std::as_const(list_images))
{
DxfPaintDevice dxf_device(file_path);
QPainter painter(&dxf_device);
painter.setWorldTransform(image->sceneTransform());
image->paintForExport(&painter);
painter.end();
}
Createdxf::dxfEnd(file_path);
diagram -> applyProperties(previous);
}
QPointF DxfExport::rotation_transformed(qreal px,
qreal py,
qreal origin_x,
qreal origin_y,
qreal angle) {
angle *= -3.14159265 / 180;
float s = sin(angle);
float c = cos(angle);
// Vector to rotate:
qreal Vx = px - origin_x;
qreal Vy = py - origin_y;
// rotate vector
float xnew = Vx * c - Vy * s;
float ynew = Vx * s + Vy * c;
return QPointF(xnew + origin_x, ynew + origin_y);
}
+42
View File
@@ -0,0 +1,42 @@
/*
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 DXFEXPORT_H
#define DXFEXPORT_H
#include <QPointF>
#include <QSize>
#include <QString>
class Diagram;
class ExportProperties;
/**
Writes a folio as a DXF file. Used by the export dialog and by
--export-dxf (discussion #1072), so both write the same file for the
same options.
*/
namespace DxfExport
{
QSize folioSize(Diagram *diagram, const ExportProperties &properties);
void write(Diagram *diagram, int width, int height,
const QString &path, const ExportProperties &properties);
QPointF rotation_transformed(qreal px, qreal py,
qreal origin_x, qreal origin_y, qreal angle);
}
#endif // DXFEXPORT_H
+4 -379
View File
@@ -19,6 +19,7 @@
#include "conductorsegment.h"
#include "createdxf.h"
#include "dxfexport.h"
#include "exportpropertieswidget.h"
#include "factory/elementpicturefactory.h"
#include "qetgraphicsitem/ViewItem/qetgraphicstableitem.h"
@@ -37,7 +38,6 @@
#include "qetmessagebox.h"
#include <QGraphicsSimpleTextItem>
#include <QSet>
#include <QSvgGenerator>
#include <QtXml>
#include <cmath>
@@ -431,382 +431,6 @@ void ExportDialog::generateSvg(
saveReloadDiagramParameters(diagram, false);
}
/**
Exporte le schema en DXF
@param diagram Schema a exporter en DXF
@param width Largeur de l'export DXF
@param height Hauteur de l'export DXF
@param file_path
*/
void ExportDialog::generateDxf(
Diagram *diagram,
int width,
int height,
QString &file_path)
{
saveReloadDiagramParameters(diagram, true);
width -= 2*Diagram::margin;
height -= 2*Diagram::margin;
Createdxf::xScale = Createdxf::sheetWidth / double(width);
Createdxf::yScale = Createdxf::sheetHeight / double(height);
Createdxf::dxfBegin(file_path);
//Add project elements (lines, rectangles, circles, texts) to dxf file
if (epw -> exportProperties().draw_border) {
QRectF rect(Diagram::margin,Diagram::margin,width,height);
Createdxf::drawRectangle(file_path,rect,0);
}
diagram -> border_and_titleblock.drawDxf(file_path, 0);
// Build the lists of elements.
QList<Element *> list_elements;
QList<Conductor *> list_conductors;
QList<DiagramTextItem *> list_texts;
QList<DiagramImageItem *> list_images;
//Slave cross-reference labels. They hang off a DynamicElementTextItem
//as plain QGraphicsTextItem children, so neither cast below picks them
//up and they were missing from the DXF entirely.
QList<QGraphicsTextItem *> list_xref_texts;
//Master-side cross-reference item (the table/cross drawn next to a
//report/master element). It paints itself with hand-written
//QPainter code across three modes (drawAsCross/drawAsContacts/
//drawAsPlcTable), so instead of hand-porting each one it's replayed
//through DxfPaintEngine, which reuses paint() unmodified.
QList<CrossRefItem *> list_master_xrefs;
QList<QLineF *> list_lines;
QList<QRectF *> list_rectangles;
//QList<QRectF *> list_ellipses;
QList <QetShapeItem *> list_shapes;
QList <QetGraphicsTableItem *> list_tables;
// QList <Terminal *> list_terminals;
// Determine les elements a "XMLiser"
// In stacking order (z, then insertion order, which a load makes the
// file's order), not items() order: that follows memory addresses, so
// the same folio came out in a different order on every export, as
// saving did before bugtracker #343. A rect query gives stacking order
// even with NoIndex; anything it misses keeps its items() place after
// the rest.
QList<QGraphicsItem *> stacked_items = diagram -> items(
QRectF(-1e9, -1e9, 2e9, 2e9), Qt::IntersectsItemBoundingRect,
Qt::AscendingOrder);
{
const QSet<QGraphicsItem *> ranked(stacked_items.cbegin(), stacked_items.cend());
for (QGraphicsItem *qgi : diagram -> items()) {
if (!ranked.contains(qgi)) {
stacked_items << qgi;
}
}
}
for (QGraphicsItem *qgi : std::as_const(stacked_items)) {
if (Element *elmt = qgraphicsitem_cast<Element *>(qgi)) {
list_elements << elmt;
} else if (Conductor *f = qgraphicsitem_cast<Conductor *>(qgi)) {
list_conductors << f;
} else if (IndependentTextItem *iti = qgraphicsitem_cast<IndependentTextItem *>(qgi)) {
list_texts << iti;
} else if (DiagramImageItem *dii = qgraphicsitem_cast<DiagramImageItem *>(qgi)) {
list_images << dii;
} else if (QetShapeItem *dii = qgraphicsitem_cast<QetShapeItem *>(qgi)) {
list_shapes << dii;
} else if (DynamicElementTextItem *deti = qgraphicsitem_cast<DynamicElementTextItem *>(qgi)) {
list_texts << deti;
if (QGraphicsTextItem *xref = deti->slaveXrefItem()) {
list_xref_texts << xref;
}
} else if (QetGraphicsTableItem *gti = qgraphicsitem_cast<QetGraphicsTableItem *>(qgi)) {
list_tables << gti;
} else if (CrossRefItem *xref = qgraphicsitem_cast<CrossRefItem *>(qgi)) {
list_master_xrefs << xref;
}
}
// Draw shapes
foreach (QetShapeItem *qsi, list_shapes) qsi->toDXF(file_path, qsi->pen());
// Draw tables
foreach (QetGraphicsTableItem *gti, list_tables) {
gti->toDXF(file_path);
}
//Draw elements
foreach(Element *elmt, list_elements)
{
double rotation_angle = elmt -> orientation() * 90;
qreal elem_pos_x = elmt -> pos().x();
qreal elem_pos_y = elmt -> pos().y();// - (diagram -> margin / 2);
ElementPictureFactory::primitives primitives = ElementPictureFactory::instance()->getPrimitives(elmt->location());
for(QGraphicsSimpleTextItem *text : primitives.m_texts)
{
qreal fontSize = text->font().pointSizeF();
if (fontSize < 0)
fontSize = text->font().pixelSize();
qreal x = elem_pos_x + text->pos().x();
qreal y = elem_pos_y + text->pos().y();
qreal angle = text -> rotation() + rotation_angle;
qreal angler = angle * M_PI/180;
int xdir = -sin(angler);
int ydir = -cos(angler);
QPointF transformed_point = rotation_transformed(x, y, elem_pos_x, elem_pos_y, -rotation_angle);
x = transformed_point.x() - ydir * fontSize * 0.5;
y = transformed_point.y() - xdir * fontSize * 0.5;
QStringList lines = text->text().split('\n');
qreal offset = fontSize * 1.6;
for (QString line : lines)
{
if (line.size() > 0 && line != "_" ) {
Createdxf::drawText(file_path, line, QPointF(x, y), fontSize, 360 - angle, 0, 0.72);
}
x += offset * xdir;
y -= offset * ydir;
}
}
for (QLineF line : primitives.m_lines)
{
QTransform t = QTransform().translate(elem_pos_x,elem_pos_y).rotate(rotation_angle);
QLineF l = t.map(line);
Createdxf::drawLine(file_path, l, 0);
}
for (QRectF rect : primitives.m_rectangles)
{
QTransform t = QTransform().translate(elem_pos_x,elem_pos_y).rotate(rotation_angle);
QRectF r = t.mapRect(rect);
Createdxf::drawRectangle(file_path,r,0);
}
for (QRectF circle_rect : primitives.m_circles)
{
QTransform t = QTransform().translate(elem_pos_x,elem_pos_y).rotate(rotation_angle);
QPointF c = t.map(QPointF(circle_rect.center().x(),circle_rect.center().y()));
Createdxf::drawCircle(file_path,c,circle_rect.width()/2,0);
}
for (QVector<QPointF> polygon : primitives.m_polygons)
{
if (polygon.size() == 0)
continue;
QTransform t = QTransform().translate(elem_pos_x,elem_pos_y).rotate(rotation_angle);
QPolygonF poly = t.map(polygon);
if(poly.isClosed())
Createdxf::drawPolygon(file_path,poly,0);
else
Createdxf::drawPolyline(file_path,poly,0);
}
// Draw arcs and ellipses
for (QVector<qreal> arc : primitives.m_arcs)
{
if (arc.size() == 0)
continue;
qreal x = (elem_pos_x + arc.at(0));
qreal y = (elem_pos_y + arc.at(1));
qreal w = arc.at(2);
qreal h = arc.at(3);
qreal startAngle = arc.at(4);
qreal spanAngle = arc .at(5);
QRectF r(x,y,w,h);
QPointF hotspot(elem_pos_x,elem_pos_y);
Createdxf::drawArcEllipse(file_path, r, startAngle, spanAngle, hotspot, rotation_angle, 0);
}
if (epw -> exportProperties().draw_terminals) {
// Draw terminals
QList<Terminal *> list_terminals = elmt->terminals();
QColor col("red");
QTransform t = QTransform().translate(elem_pos_x,elem_pos_y).rotate(rotation_angle);
foreach(Terminal *tp, list_terminals) {
QPointF c = t.map(QPointF(tp->dock_elmt_.x(),tp->dock_elmt_.y()));
Createdxf::drawCircle(file_path,c,3.0,Createdxf::dxfColor(col));
}
}
}
//Draw conductors
foreach(Conductor *cond, list_conductors) {
QPolygonF poly;
bool firstseg = true;
foreach(ConductorSegment *segment, cond -> segmentsList()) {
//Createdxf::drawLine(file_path,QLineF(cond->pos()+segment->firstPoint(),cond->pos()+segment->secondPoint()),0);
if(firstseg){
poly << cond->pos()+segment->firstPoint();
firstseg = false;
}
poly << cond->pos()+segment->secondPoint();
}
Createdxf::drawPolyline(file_path,poly,0);
//Draw conductor text item
ConductorTextItem *textItem = cond -> textItem();
if (textItem) {
qreal fontSize = textItem -> font().pointSizeF();
if (fontSize < 0)
fontSize = textItem -> font().pixelSize();
qreal angle = textItem -> rotation();
qreal angler = angle * M_PI/180;
int xdir = -sin(angler);
int ydir = -cos(angler);
qreal x = (cond->pos().x() + textItem -> pos().x())
+ xdir * fontSize * 1.8
- ydir * fontSize;
qreal y = (cond->pos().y() + textItem -> pos().y())
- ydir * fontSize * 1.8
- xdir * fontSize * 0.9;
QStringList lines = textItem->toPlainText().split('\n');
qreal offset = fontSize * 1.6;
foreach (QString line, lines) {
if (line.size() > 0 && line != "_" )
Createdxf::drawText(file_path, line, QPointF(x, y), fontSize, 360-angle, 0, 0.72 );
x += offset * xdir;
y -= offset * ydir;
}
}
// Draw the junctions
QList<QPointF> junctions_list = cond->junctions();
if (!junctions_list.isEmpty()) {
foreach(QPointF point, junctions_list) {
Createdxf::drawEllipse(file_path,QRectF(cond->pos().x() + point.x() - 1.5, cond->pos().y() + point.y() - 1.5, 3.0, 3.0),0);
}
}
}
//Draw text items
foreach(DiagramTextItem *dti, list_texts) {
qreal fontSize = dti -> font().pointSizeF();
if (fontSize < 0)
fontSize = dti -> font().pixelSize();
qreal angle = dti -> rotation();
QGraphicsItem *parent = dti->parentItem();
while (parent) {
angle += parent->rotation();
parent = parent->parentItem();
}
qreal angler = angle * M_PI/180;
int xdir = -sin(angler);
int ydir = -cos(angler);
qreal x = (dti->scenePos().x())
+ xdir * fontSize * 1.8
- ydir * fontSize;
qreal y = dti->scenePos().y()
- ydir * fontSize * 1.8
- xdir * fontSize * 0.9;
QStringList lines = dti -> toPlainText().split('\n');
qreal offset = fontSize * 1.6;
foreach (QString line, lines) {
if (line.size() > 0 && line != "_" )
Createdxf::drawText(file_path, line, QPointF(x, y), fontSize, 360-angle, Createdxf::dxfColor(dti->color()), 0.72 );
x += offset * xdir;
y -= offset * ydir;
}
}
//Draw the slave cross-reference labels
for (QGraphicsTextItem *xref : std::as_const(list_xref_texts))
{
qreal fontSize = xref->font().pointSizeF();
if (fontSize < 0)
fontSize = xref->font().pixelSize();
qreal angle = xref->rotation();
QGraphicsItem *parent = xref->parentItem();
while (parent) {
angle += parent->rotation();
parent = parent->parentItem();
}
qreal angler = angle * M_PI/180;
int xdir = -sin(angler);
int ydir = -cos(angler);
qreal x = xref->scenePos().x()
+ xdir * fontSize * 1.8
- ydir * fontSize;
qreal y = xref->scenePos().y()
- ydir * fontSize * 1.8
- xdir * fontSize * 0.9;
const QStringList lines = xref->toPlainText().split('\n');
const qreal offset = fontSize * 1.6;
for (const QString &line : lines) {
if (line.size() > 0 && line != QLatin1String("_")) {
Createdxf::drawText(file_path, line, QPointF(x, y), fontSize,
360-angle, Createdxf::dxfColor(xref->defaultTextColor()), 0.72);
}
x += offset * xdir;
y -= offset * ydir;
}
}
//Draw the master-side cross-reference items (table/cross), replaying
//their existing paint() unmodified through DxfPaintEngine instead of
//hand-porting drawAsCross()/drawAsContacts()/drawAsPlcTable().
for (CrossRefItem *xref : std::as_const(list_master_xrefs))
{
DxfPaintDevice dxf_device(file_path);
QPainter painter(&dxf_device);
painter.setWorldTransform(xref->sceneTransform());
xref->paintForExport(&painter);
painter.end();
}
//Draw images -- collected above (list_images) but never actually
//drawn until now, an existing gap this reuses the same paint()
//-replay approach to fix: DiagramImageItem::paint() has no
//viewport-dependent logic (unlike CrossRefItem, which needs its own
//paintForExport() for that reason), so it's called directly with a
//default QStyleOptionGraphicsItem rather than needing an export-
//specific variant of its own. DxfPaintEngine::drawPixmap() is what
//actually turns the drawPixmap() call inside paint() into a
//placeholder outline, since this DXF dialect has no raster image
//entity to draw instead.
for (DiagramImageItem *image : std::as_const(list_images))
{
DxfPaintDevice dxf_device(file_path);
QPainter painter(&dxf_device);
painter.setWorldTransform(image->sceneTransform());
image->paintForExport(&painter);
painter.end();
}
Createdxf::dxfEnd(file_path);
saveReloadDiagramParameters(diagram, false);
}
QPointF ExportDialog::rotation_transformed(qreal px,
qreal py,
qreal origin_x,
qreal origin_y,
qreal angle) {
angle *= -3.14159265 / 180;
float s = sin(angle);
float c = cos(angle);
// Vector to rotate:
qreal Vx = px - origin_x;
qreal Vy = py - origin_y;
// rotate vector
float xnew = Vx * c - Vy * s;
float ynew = Vx * s + Vy * c;
return QPointF(xnew + origin_x, ynew + origin_y);
}
/**
Slot effectuant les exports apres la validation du dialogue.
@@ -952,11 +576,12 @@ void ExportDialog::exportDiagram(ExportDiagramLine *diagram_line) {
target_file
);
} else if (format_acronym == "DXF") {
generateDxf(
DxfExport::write(
diagram_line -> diagram,
diagram_line -> width -> value(),
diagram_line -> height -> value(),
diagram_path
diagram_path,
export_properties
);
} else {
QImage image = generateImage(
-2
View File
@@ -37,7 +37,6 @@ class ExportDialog : public QDialog {
// methods
int diagramsToExportCount() const;
static QPointF rotation_transformed(qreal, qreal, qreal, qreal, qreal);
private:
ExportDialog(const ExportDialog &);
@@ -91,7 +90,6 @@ class ExportDialog : public QDialog {
QWidget *initDiagramsListPart();
void saveReloadDiagramParameters(Diagram *, bool = true);
void generateSvg(Diagram *, int, int, bool, QIODevice &);
void generateDxf(Diagram *, int, int, QString &);
QImage generateImage(Diagram *, int, int, bool);
void exportDiagram(ExportDiagramLine *);
qreal diagramRatio(Diagram *);
+1 -1
View File
@@ -2070,7 +2070,7 @@ void QETDiagramEditor::selectionGroupTriggered(QAction *action)
//nothing to rotate.
if (RotateTextsCommand::hasSelectedTexts(diagram))
{
qreal rotation = 0;
qreal rotation = RotateTextsCommand::currentRotation(diagram);
if (RotateTextsCommand::askRotation(rotation))
diagram->undoStack().push(new RotateTextsCommand(diagram, rotation));
}
@@ -19,6 +19,7 @@
#include <algorithm>
#include "../../dataBase/legacyelementtypes.h"
#include "../../dataBase/projectdatabase.h"
#include "../../qetapp.h"
#include "../../qetinformation.h"
@@ -294,7 +295,9 @@ void ProjectDBModel::fromXml(const QDomElement &element)
return;
setIdentifier(element.firstChildElement("identifier").text());
setQuery(element.firstChildElement("query").text());
//A query saved before June 2022 names element types the old way
setQuery(LegacyElementTypes::upgradeQuery(
element.firstChildElement("query").text()));
//Index 0,0
auto index_00 = element.firstChildElement("index00");
+7
View File
@@ -207,6 +207,13 @@ bool QetScriptApi::exportSvg(const QString &outDir, bool showTerminals)
return runFlag(QStringLiteral("--export-svg"), args);
}
bool QetScriptApi::exportDxf(const QString &outDir, bool showTerminals)
{
QStringList args{outDir};
if (showTerminals) args << QStringLiteral("--show-terminals");
return runFlag(QStringLiteral("--export-dxf"), args);
}
bool QetScriptApi::exportCables(const QString &output)
{
return runFlag(QStringLiteral("--export-cables"), {output});
+1
View File
@@ -360,6 +360,7 @@ class QetScriptApi : public QObject
Q_INVOKABLE bool exportPdf(const QString &output, bool showTerminals = false);
Q_INVOKABLE bool exportPng(const QString &outDir, bool showTerminals = false);
Q_INVOKABLE bool exportSvg(const QString &outDir, bool showTerminals = false);
Q_INVOKABLE bool exportDxf(const QString &outDir, bool showTerminals = false);
Q_INVOKABLE bool exportCables(const QString &output);
Q_INVOKABLE bool exportWires(const QString &output);
Q_INVOKABLE bool exportBom(const QString &output);
@@ -70,6 +70,7 @@ GeneralConfigurationPage::GeneralConfigurationPage(QWidget *parent) :
//Stored as "inserts" but presented as "edits", so the default (insert)
//is the unchecked state -- a preference reads better as an opt-out.
ui->m_collection_dblclick_edits->setChecked(!settings.value("elementscollection/double-click-inserts", true).toBool());
ui->m_collection_search_flat_cb->setChecked(settings.value("elementscollection/search-flat-list", true).toBool());
ui->m_context_toolbar_cb->setChecked(settings.value("diagrameditor/context_toolbar", true).toBool());
ui->m_mouse_gestures_cb->setChecked(settings.value("diagrameditor/mouse_gestures", true).toBool());
ui->DiagramEditor_xGrid_sb->setValue(settings.value("diagrameditor/Xgrid", 10).toInt());
@@ -302,6 +303,7 @@ void GeneralConfigurationPage::applyConf()
settings.setValue("diagrameditor/grid_display_startup", ui->grid_startup_cb->isChecked());
settings.setValue("diagrameditor/guides_display_startup", ui->guides_startup_cb->isChecked());
settings.setValue("elementscollection/double-click-inserts", !ui->m_collection_dblclick_edits->isChecked());
settings.setValue("elementscollection/search-flat-list", ui->m_collection_search_flat_cb->isChecked());
settings.setValue("diagrameditor/context_toolbar", ui->m_context_toolbar_cb->isChecked());
settings.setValue("diagrameditor/mouse_gestures", ui->m_mouse_gestures_cb->isChecked());
//Grid step and key navigation
@@ -94,6 +94,16 @@
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="m_collection_search_flat_cb">
<property name="toolTip">
<string>Coché : une seule liste, la meilleure correspondance en premier. Décoché : l'arborescence de la collection, filtrée sur la recherche.</string>
</property>
<property name="text">
<string>Afficher les résultats de recherche sous forme de liste triée</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="m_context_toolbar_cb">
<property name="toolTip">
+34 -2
View File
@@ -25,6 +25,8 @@
#include "../qetgraphicsitem/elementtextitemgroup.h"
#include "../qtextorientationspinboxwidget.h"
#include <cmath>
/**
@brief RotateTextsCommand::hasSelectedTexts
@param diagram
@@ -41,6 +43,35 @@ bool RotateTextsCommand::hasSelectedTexts(Diagram *diagram)
return (!dc.selectedTexts().isEmpty() || !dc.selectedTextsGroup().isEmpty());
}
/**
@brief RotateTextsCommand::currentRotation
@param diagram
@return the rotation shared by every selected text and text group of
@p diagram, so the dialog can open at the angle they already have.
0 when nothing is selected or when their angles differ.
*/
qreal RotateTextsCommand::currentRotation(Diagram *diagram)
{
if(!diagram)
return 0;
DiagramContent dc(diagram);
QList<qreal> angles;
for(DiagramTextItem *dti : dc.selectedTexts())
angles << dti->rotation();
for(ElementTextItemGroup *etig : dc.selectedTextsGroup())
angles << etig->rotation();
if(angles.isEmpty())
return 0;
for(qreal angle : angles)
if(qAbs(angle - angles.first()) > 0.01)
return 0;
//The dialog's spin box accepts -360 to 360
return std::fmod(angles.first(), 360);
}
/**
@brief RotateTextsCommand::RotateTextsCommand
@param diagram : Apply the rotation to the selected texts and group of texts
@@ -124,8 +155,8 @@ void RotateTextsCommand::redo()
/**
@brief RotateTextsCommand::askRotation
Ask the user for an orientation.
@param rotation : set to the chosen angle when the dialog is accepted,
left untouched otherwise.
@param rotation : the angle the dialog opens at; set to the chosen
angle when the dialog is accepted, left untouched otherwise.
@return true if the user accepted, false if they cancelled.
Deliberately static and separate from the command: a QUndoCommand that
@@ -145,6 +176,7 @@ bool RotateTextsCommand::askRotation(qreal &rotation)
QTextOrientationSpinBoxWidget *ori_widget = QETApp::createTextOrientationSpinBoxWidget();
ori_widget->setParent(&ori_text_dialog);
ori_widget->setOrientation(rotation);
ori_widget->spinBox()->selectAll();
QDialogButtonBox buttons(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
+4 -2
View File
@@ -40,7 +40,7 @@ class QParallelAnimationGroup;
Typical interactive use:
@code
if (RotateTextsCommand::hasSelectedTexts(diagram)) {
qreal rotation = 0;
qreal rotation = RotateTextsCommand::currentRotation(diagram);
if (RotateTextsCommand::askRotation(rotation))
diagram->undoStack().push(new RotateTextsCommand(diagram, rotation));
}
@@ -53,7 +53,9 @@ class RotateTextsCommand : public QUndoCommand
/// @return true if @p diagram has at least one selected text or text group to rotate.
static bool hasSelectedTexts(Diagram *diagram);
/// Open the orientation dialog. @return true and set @p rotation if accepted, false if cancelled.
/// @return the angle the selected texts share, or 0 if they differ.
static qreal currentRotation(Diagram *diagram);
/// Open the orientation dialog at @p rotation. @return true and set @p rotation if accepted, false if cancelled.
static bool askRotation(qreal &rotation);
void undo() override;
+6
View File
@@ -117,6 +117,12 @@ add_executable(tst_itemgroups tst_itemgroups.cpp ${QET_DIR}/sources/itemgroups.c
add_test(NAME tst_itemgroups COMMAND tst_itemgroups)
target_include_directories(tst_itemgroups PRIVATE ${QET_DIR}/sources)
target_link_libraries(tst_itemgroups PRIVATE Qt::Test Qt::Widgets Qt::Xml)
# legacyelementtypes.h is header-only: the rewrite of element type names in
# nomenclature queries saved before June 2022.
add_executable(tst_legacyelementtypes tst_legacyelementtypes.cpp)
add_test(NAME tst_legacyelementtypes COMMAND tst_legacyelementtypes)
target_include_directories(tst_legacyelementtypes PRIVATE ${QET_DIR}/sources)
target_link_libraries(tst_legacyelementtypes PRIVATE Qt::Test)
add_executable(
tst_qetpalette
+60
View File
@@ -0,0 +1,60 @@
/*
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 "dataBase/legacyelementtypes.h"
#include <QTest>
class TestLegacyElementTypes : public QObject
{
Q_OBJECT
private slots:
void upgradesOldNames_data()
{
QTest::addColumn<QString>("saved");
QTest::addColumn<QString>("expected");
// The WHERE clause industrial.qet's parts list was saved with.
QTest::newRow("simple, with sub types")
<< "WHERE ( element_type = 'Simple' OR element_sub_type = 'coil') ORDER BY label"
<< "WHERE ( element_type = 'simple' OR element_sub_type = 'coil') ORDER BY label";
QTest::newRow("terminal and master, lengths change")
<< "element_type = 'Terminale' OR element_type = 'Master' OR element_type = 'Simple'"
<< "element_type = 'terminal' OR element_type = 'master' OR element_type = 'simple'";
QTest::newRow("reports and thumbnail")
<< "element_type='NextReport' OR element_type = 'PreviousReport' OR element_type = 'Thumbnail'"
<< "element_type='next_report' OR element_type = 'previous_report' OR element_type = 'thumbnail'";
QTest::newRow("current names unchanged")
<< "element_type = 'simple' OR element_type = 'terminal'"
<< "element_type = 'simple' OR element_type = 'terminal'";
QTest::newRow("the word elsewhere unchanged")
<< "WHERE label = 'Simple' OR designation = 'Master'"
<< "WHERE label = 'Simple' OR designation = 'Master'";
QTest::newRow("empty") << "" << "";
}
void upgradesOldNames()
{
QFETCH(QString, saved);
QFETCH(QString, expected);
QCOMPARE(LegacyElementTypes::upgradeQuery(saved), expected);
}
};
QTEST_APPLESS_MAIN(TestLegacyElementTypes)
#include "tst_legacyelementtypes.moc"