Merge pull request #637 from ispyisail/feature-elmt-export-svg

Add "Export to SVG" to the element editor (discussion #605)
This commit is contained in:
Laurent Trinques
2026-08-03 16:34:54 +02:00
committed by GitHub
5 changed files with 115 additions and 0 deletions
+4
View File
@@ -317,6 +317,10 @@ void ElementScene::contextMenuEvent(QGraphicsSceneContextMenuEvent *event)
*/
void ElementScene::drawForeground(QPainter *p, const QRectF &)
{
if (!m_hotspot_visible) {
return;
}
p -> save();
// desactive tout antialiasing, sauf pour le texte
+10
View File
@@ -90,6 +90,8 @@ class ElementScene : public QGraphicsScene
m_y_grid;
QPointer<CustomElementGraphicPart> m_single_selected_item;
bool m_hotspot_visible = true;
// methods
public:
@@ -132,6 +134,14 @@ class ElementScene : public QGraphicsScene
QETElementEditor* editor() const;
void addItems(QVector<QGraphicsItem *> items);
void removeItems(QVector<QGraphicsItem *> items);
/// Whether drawForeground() draws the red hotspot cross. On by
/// default (the normal editing view); turned off around a
/// render() call meant to capture the element's actual content
/// only, e.g. exporting to SVG -- the cross is an editing aid,
/// not part of the drawn symbol.
void setHotspotVisible(bool visible) {m_hotspot_visible = visible;}
bool hotspotVisible() const {return m_hotspot_visible;}
protected:
void mouseMoveEvent (QGraphicsSceneMouseEvent *) override;
+90
View File
@@ -53,6 +53,8 @@
#include <QSettings>
#include <QActionGroup>
#include <QFileDialog>
#include <QSvgGenerator>
/**
* @brief QETElementEditor::QETElementEditor
@@ -1377,6 +1379,94 @@ bool QETElementEditor::on_m_save_as_file_action_triggered()
return false;
}
/**
@brief QETElementEditor::on_m_export_svg_action_triggered
Export the element currently open in this editor to a standalone SVG
file.
Renders the live ElementScene directly, the same way
ExportDialog::generateSvg() renders the live Diagram for the diagram
editor's own SVG export -- not ElementPictureFactory's cached picture.
That cache is keyed by the element's saved-to-disk uuid and is never
invalidated on edit (nothing in this editor ever tells it to), so it
would silently export stale content for any element already previewed
once in the elements panel, and nothing at all for one that has never
been saved. Rendering the scene directly has neither problem and
always reflects exactly what is currently on screen, saved or not.
@return true if the file was written
*/
bool QETElementEditor::on_m_export_svg_action_triggered()
{
//Suggest the element's own filename (without its .elmt extension) as
//the default export name, per plc-user's review on #637 -- the
//directory-only default below is unchanged for an element that has
//never been saved, since there is no filename to derive one from.
QString suggested_path = QETApp::customElementsDir();
if (!m_file_name.isEmpty()) {
QFileInfo file_info(m_file_name);
suggested_path = QDir(file_info.absolutePath()).filePath(file_info.completeBaseName());
}
QString fn = QFileDialog::getSaveFileName(
this,
tr("Exporter en SVG", "dialog title"),
suggested_path,
tr("Image SVG (*.svg)", "filetypes allowed when exporting an element to SVG"));
if (fn.isEmpty()) {
return false;
}
if (!fn.endsWith(".svg", Qt::CaseInsensitive)) {
fn += ".svg";
}
QFile file(fn);
if (!file.open(QIODevice::WriteOnly)) {
QMessageBox::critical(this, tr("Échec de l'export"),
tr("Impossible d'écrire dans le fichier « %1 ».").arg(fn));
return false;
}
//Margin-less bounding rect of the element's own drawn content
//(lines, rects, terminals, text...), excluding the origin cross
//and other editor-only decoration -- see
//ElementScene::elementSceneGeometricRect()'s own doc comment.
//Falls back to itemsBoundingRect() for the rare element made up
//only of item types that helper deliberately excludes.
QRectF source_rect = m_elmt_scene->elementSceneGeometricRect();
if (source_rect.isEmpty()) {
source_rect = m_elmt_scene->itemsBoundingRect();
}
constexpr qreal margin = 5.0;
source_rect.adjust(-margin, -margin, margin, margin);
QSize target_size = source_rect.size().toSize();
if (target_size.isEmpty()) {
target_size = QSize(1, 1);
}
QSvgGenerator svg_engine;
svg_engine.setSize(target_size);
svg_engine.setViewBox(QRect(QPoint(0, 0), target_size));
svg_engine.setOutputDevice(&file);
QPainter svg_painter(&svg_engine);
svg_painter.setRenderHint(QPainter::Antialiasing, true);
svg_painter.setRenderHint(QPainter::TextAntialiasing, true);
//The hotspot cross is ElementScene::drawForeground()'s editing aid,
//drawn unconditionally on every render() call including this one
//unless told not to -- it is not part of the element being
//exported.
m_elmt_scene->setHotspotVisible(false);
m_elmt_scene->render(&svg_painter, QRectF(QPointF(0, 0), target_size), source_rect);
m_elmt_scene->setHotspotVisible(true);
svg_painter.end();
return true;
}
void QETElementEditor::on_m_reload_action_triggered()
{
//If user already edit the element, ask confirmation to reload
+1
View File
@@ -87,6 +87,7 @@ class QETElementEditor : public QMainWindow
void on_m_open_action_triggered();
void on_m_open_from_file_action_triggered();
bool on_m_save_as_file_action_triggered();
bool on_m_export_svg_action_triggered();
void on_m_reload_action_triggered();
void on_m_quit_action_triggered();
void on_m_deselect_all_action_triggered();
+10
View File
@@ -40,6 +40,7 @@
<addaction name="m_save_action"/>
<addaction name="m_save_as_action"/>
<addaction name="m_save_as_file_action"/>
<addaction name="m_export_svg_action"/>
<addaction name="separator"/>
<addaction name="m_reload_action"/>
<addaction name="separator"/>
@@ -262,6 +263,15 @@
<string>Enregistrer dans un fichier</string>
</property>
</action>
<action name="m_export_svg_action">
<property name="icon">
<iconset resource="../../../qelectrotech.qrc">
<normaloff>:/ico/22x22/document-export.png</normaloff>:/ico/22x22/document-export.png</iconset>
</property>
<property name="text">
<string>Exporter en SVG</string>
</property>
</action>
<action name="m_reload_action">
<property name="icon">
<iconset resource="../../../qelectrotech.qrc">