Merge pull request #772 from Kellermorph/pdf-import

Add PDF page import as image
This commit is contained in:
Laurent Trinques
2026-08-22 19:30:33 +02:00
committed by GitHub
10 changed files with 579 additions and 0 deletions
+245
View File
@@ -0,0 +1,245 @@
/*
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 "diagrameventaddpdf.h"
#include "../qetapp.h"
#include "../diagram.h"
#include "../undocommand/addgraphicsobjectcommand.h"
#include "../qetgraphicsitem/diagramimageitem.h"
#include "../ui/pdfpagesdialog.h"
#include <QPdfDocument>
#include <QFileDialog>
#include <QMessageBox>
#include <QPainter>
/**
@brief DiagramEventAddPdf::DiagramEventAddPdf
Constructor
@param diagram the diagram where this event operates
*/
DiagramEventAddPdf::DiagramEventAddPdf(Diagram *diagram) :
DiagramEventInterface(diagram),
m_image(nullptr),
m_is_added(false)
{
openDialog();
}
/**
@brief DiagramEventAddPdf::~DiagramEventAddPdf
Destructor
*/
DiagramEventAddPdf::~DiagramEventAddPdf()
{
if (m_running || m_abort)
{
if (m_is_added) m_diagram->removeItem(m_image);
delete m_image;
}
foreach (QGraphicsView *view, m_diagram->views())
view->setContextMenuPolicy((Qt::DefaultContextMenu));
}
/**
@brief DiagramEventAddPdf::mousePressEvent
Action when mouse is pressed
@param event event of mouse pressed
*/
void DiagramEventAddPdf::mousePressEvent(QGraphicsSceneMouseEvent *event)
{
if (m_image && event->button() == Qt::LeftButton)
{
QPointF pos = event->scenePos();
pos.rx() -= m_image->boundingRect().width()/2;
pos.ry() -= m_image->boundingRect().height()/2;
m_diagram->undoStack().push(new AddGraphicsObjectCommand(m_image, m_diagram, pos));
for (QGraphicsView *view : m_diagram->views()) {
view->setContextMenuPolicy((Qt::DefaultContextMenu));
}
m_running = false;
emit finish();
event->setAccepted(true);
}
else if (m_image && event->button() == Qt::RightButton)
{
m_image->setRotation(m_image->rotation() + 90);
event->setAccepted(true);
}
}
/**
@brief DiagramEventAddPdf::mouseMoveEvent
Action when mouse moves
@param event event of mouse move
*/
void DiagramEventAddPdf::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
{
if (!m_image || event->buttons() != Qt::NoButton) {
return;
}
QPointF pos = event->scenePos();
if (!m_is_added)
{
for (QGraphicsView *view : m_diagram->views()) {
view->setContextMenuPolicy((Qt::NoContextMenu));
}
m_diagram->addItem(m_image);
m_is_added = true;
}
m_image->setPos(pos - m_image->boundingRect().center());
event->setAccepted(true);
}
/**
@brief DiagramEventAddPdf::mouseDoubleClickEvent
Overwrite double click to prevent opening properties dialog.
@param event event of mouse double click
*/
void DiagramEventAddPdf::mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event) {
event->setAccepted(true);
}
/**
@brief DiagramEventAddPdf::wheelEvent
Action when mouse wheel is rotated (CTRL+wheel to scale)
@param event event of mouse wheel
*/
void DiagramEventAddPdf::wheelEvent(QGraphicsSceneWheelEvent *event)
{
if (!m_is_added || !m_image || event->modifiers() != Qt::CTRL) {
return;
}
qreal scaling = m_image->scale();
event->delta() > 1 ? scaling += 0.01 : scaling -= 0.01;
if (scaling > 0.01 && scaling <= 2) {
m_image->setScale(scaling);
}
event->setAccepted(true);
}
/**
@brief DiagramEventAddPdf::isNull
@return true if the PDF image couldn't be loaded, false otherwise
*/
bool DiagramEventAddPdf::isNull() const
{
if (!m_image) return true;
return false;
}
/**
@brief DiagramEventAddPdf::openDialog
Opens a file dialog to select a PDF file, then opens a page selection
dialog with DPI options. The selected page is rendered to a QImage at
the chosen DPI and converted to a DiagramImageItem.
*/
void DiagramEventAddPdf::openDialog()
{
if (m_diagram->isReadOnly()) return;
// Open file dialog to select a PDF file
QString pathPictures = QETApp::pictureDir();
QString fileName = QFileDialog::getOpenFileName(
m_diagram->views().isEmpty() ? nullptr : m_diagram->views().first(),
QObject::tr("Sélectionner un fichier PDF..."),
pathPictures,
QObject::tr("Fichiers PDF (*.pdf)")
);
if (fileName.isEmpty()) return;
// Load the PDF document
QPdfDocument document;
document.load(fileName);
if (document.status() != QPdfDocument::Status::Ready)
{
QMessageBox::critical(
m_diagram->views().isEmpty() ? nullptr : m_diagram->views().first(),
QObject::tr("Erreur"),
QObject::tr("Impossible de charger le fichier PDF.")
);
return;
}
int pageCount = document.pageCount();
if (pageCount <= 0)
{
QMessageBox::critical(
m_diagram->views().isEmpty() ? nullptr : m_diagram->views().first(),
QObject::tr("Erreur"),
QObject::tr("Le fichier PDF ne contient aucune page.")
);
return;
}
// Always show the dialog so the user can choose page and DPI
PdfPagesDialog dialog(document,
m_diagram->views().isEmpty() ? nullptr : m_diagram->views().first());
if (dialog.exec() != QDialog::Accepted) return;
int pageIndex = dialog.selectedPage() - 1; // Convert to 0-based index
int dpi = dialog.selectedDpi();
// Calculate pixel size from PDF points at the user-selected DPI
// PDF point = 1/72 inch
QSizeF pageSize = document.pagePointSize(pageIndex);
int pixelWidth = qRound((pageSize.width() / 72.0) * dpi);
int pixelHeight = qRound((pageSize.height() / 72.0) * dpi);
if (pixelWidth <= 0 || pixelHeight <= 0)
{
QMessageBox::critical(
m_diagram->views().isEmpty() ? nullptr : m_diagram->views().first(),
QObject::tr("Erreur"),
QObject::tr("Impossible de déterminer la taille de la page PDF.")
);
return;
}
// Render PDF page to QImage
QImage image = document.render(pageIndex, QSize(pixelWidth, pixelHeight));
if (image.isNull())
{
QMessageBox::critical(
m_diagram->views().isEmpty() ? nullptr : m_diagram->views().first(),
QObject::tr("Erreur"),
QObject::tr("Impossible de rendre la page PDF.")
);
return;
}
// Fill white background to handle transparent PDFs
QImage background(image.size(), QImage::Format_ARGB32_Premultiplied);
background.fill(Qt::white);
QPainter painter(&background);
painter.drawImage(0, 0, image);
painter.end();
m_image = new DiagramImageItem(QPixmap::fromImage(background));
m_running = true;
}
+55
View File
@@ -0,0 +1,55 @@
/*
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 DIAGRAMEVENTADDPDF_H
#define DIAGRAMEVENTADDPDF_H
#include "diagrameventinterface.h"
class Diagram;
class DiagramImageItem;
/**
@brief The DiagramEventAddPdf class
This diagram event handles the adding of a PDF page as an image in a diagram.
The selected PDF page is rendered to a QImage at 150 DPI and added as a
DiagramImageItem. The placement, rotation and scaling behavior is identical
to the existing image import.
*/
class DiagramEventAddPdf : public DiagramEventInterface
{
Q_OBJECT
public:
DiagramEventAddPdf(Diagram *diagram);
~DiagramEventAddPdf() override;
void mousePressEvent (QGraphicsSceneMouseEvent *event) override;
void mouseMoveEvent (QGraphicsSceneMouseEvent *event) override;
void mouseDoubleClickEvent (QGraphicsSceneMouseEvent *event) override;
void wheelEvent (QGraphicsSceneWheelEvent *event) override;
bool isNull() const;
private:
void openDialog();
DiagramImageItem *m_image;
bool m_is_added;
};
#endif // DIAGRAMEVENTADDPDF_H
+25
View File
@@ -23,6 +23,9 @@
#include "conductornumexport.h"
#include "diagramcommands.h"
#include "diagramevent/diagrameventaddimage.h"
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
#include "diagramevent/diagrameventaddpdf.h"
#endif
#include "diagramevent/diagrameventaddshape.h"
#include "diagramevent/diagrameventaddtext.h"
#include "diagramview.h"
@@ -716,6 +719,9 @@ void QETDiagramEditor::setUpActions()
//Adding action (add text, image, shape...)
QAction *add_text = m_add_item_actions_group.addAction(QET::Icons::PartTextField, tr("Ajouter un champ de texte"));
QAction *add_image = m_add_item_actions_group.addAction(QET::Icons::adding_image, tr("Ajouter une image"));
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
QAction *add_pdf = m_add_item_actions_group.addAction(QET::Icons::adding_pdf, tr("Ajouter un PDF"));
#endif
QAction *add_line = m_add_item_actions_group.addAction(QET::Icons::PartLine, tr("Ajouter une ligne", "Draw line"));
QAction *add_rectangle = m_add_item_actions_group.addAction(QET::Icons::PartRectangle, tr("Ajouter un rectangle"));
QAction *add_ellipse = m_add_item_actions_group.addAction(QET::Icons::PartEllipse, tr("Ajouter une ellipse"));
@@ -724,6 +730,9 @@ void QETDiagramEditor::setUpActions()
add_text ->setStatusTip(tr("Ajoute un champ de texte sur le folio actuel"));
add_image ->setStatusTip(tr("Ajoute une image sur le folio actuel"));
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
add_pdf ->setStatusTip(tr("Ajoute une page PDF sur le folio actuel"));
#endif
add_line ->setStatusTip(tr("Ajoute une ligne sur le folio actuel"));
add_rectangle->setStatusTip(tr("Ajoute un rectangle sur le folio actuel"));
add_ellipse ->setStatusTip(tr("Ajoute une ellipse sur le folio actuel"));
@@ -732,6 +741,9 @@ void QETDiagramEditor::setUpActions()
add_text ->setData(QStringLiteral("text"));
add_image ->setData(QStringLiteral("image"));
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
add_pdf ->setData(QStringLiteral("pdf"));
#endif
add_line ->setData(QStringLiteral("line"));
add_rectangle->setData(QStringLiteral("rectangle"));
add_ellipse ->setData(QStringLiteral("ellipse"));
@@ -1569,6 +1581,19 @@ void QETDiagramEditor::addItemGroupTriggered(QAction *action)
else
diagram_event = deai;
}
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
else if (value == "pdf")
{
DiagramEventAddPdf *deap = new DiagramEventAddPdf(d);
if (deap->isNull())
{
delete deap;
return;
}
else
diagram_event = deap;
}
#endif
else if (value == "text")
{
diagram_event = new DiagramEventAddText(d);
+2
View File
@@ -183,6 +183,7 @@ namespace QET {
QIcon ZoomOriginal;
QIcon ZoomOut;
QIcon adding_image;
QIcon adding_pdf;
QIcon resize_image;
QIcon sa;
QIcon br;
@@ -453,6 +454,7 @@ void QET::Icons::initIcons()
EditClear .addFile(":/ico/22x22/edit-clear.png");
EditText .addFile(":/ico/22x22/names.png");
adding_image .addFile(":/ico/22x22/insert-image.png");
adding_pdf .addFile(":/ico/22x22/pdf-import.png");
if (rtl) {
EditClearLocationBar.addPixmap(QPixmap(":/ico/16x16/edit-clear-locationbar-ltr.png").transformed(reverse));
+1
View File
@@ -191,6 +191,7 @@ namespace QET {
extern QIcon ZoomOriginal;
extern QIcon ZoomOut;
extern QIcon adding_image;
extern QIcon adding_pdf;
extern QIcon resize_image;
extern QIcon sa;
extern QIcon br;
+176
View File
@@ -0,0 +1,176 @@
/*
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 "pdfpagesdialog.h"
#include <QVBoxLayout>
#include <QHBoxLayout>
#include <QLabel>
#include <QSpinBox>
#include <QComboBox>
#include <QPushButton>
#include <QDialogButtonBox>
#include <QPdfDocument>
#include <QScrollArea>
#include <QPainter>
// Preview rendering DPI for the thumbnail in the dialog.
// Fixed at 96 DPI to keep the preview fast and compact regardless
// of the final render DPI selected by the user.
static const int PREVIEW_DPI = 96;
static const int PREVIEW_WIDTH = 400;
/**
@brief PdfPagesDialog::PdfPagesDialog
Constructor
@param document the loaded PDF document to preview pages from
@param parent parent widget
*/
PdfPagesDialog::PdfPagesDialog(QPdfDocument &document, QWidget *parent)
: QDialog(parent),
m_document(document)
{
setWindowTitle(tr("Sélectionner une page"));
setMinimumWidth(500);
setMinimumHeight(500);
int pageCount = document.pageCount();
// Main layout
QVBoxLayout *main_layout = new QVBoxLayout(this);
// Info label: "Ce document PDF contient X page(s)"
m_info_label = new QLabel(
tr("Ce document PDF contient %n page(s)", "", pageCount),
this
);
m_info_label->setWordWrap(true);
main_layout->addWidget(m_info_label);
// Page selection layout
QHBoxLayout *page_layout = new QHBoxLayout();
QLabel *select_label = new QLabel(tr("Page à importer :"), this);
page_layout->addWidget(select_label);
// Spinbox: min=1, max=pageCount
m_page_spinbox = new QSpinBox(this);
m_page_spinbox->setMinimum(1);
m_page_spinbox->setMaximum(pageCount);
m_page_spinbox->setValue(1);
page_layout->addWidget(m_page_spinbox);
page_layout->addStretch();
// DPI selection
QLabel *dpi_label = new QLabel(tr("Résolution :"), this);
page_layout->addWidget(dpi_label);
m_dpi_combo = new QComboBox(this);
m_dpi_combo->addItem(tr("150 DPI (écran)"), 150);
m_dpi_combo->addItem(tr("300 DPI (impression)"), 300);
m_dpi_combo->addItem(tr("600 DPI (haute qualité)"), 600);
m_dpi_combo->setCurrentIndex(0);
page_layout->addWidget(m_dpi_combo);
main_layout->addLayout(page_layout);
// Preview area
QLabel *preview_title = new QLabel(tr("Aperçu :"), this);
main_layout->addWidget(preview_title);
QScrollArea *scroll_area = new QScrollArea(this);
scroll_area->setWidgetResizable(true);
scroll_area->setAlignment(Qt::AlignCenter);
scroll_area->setMinimumHeight(250);
m_preview_label = new QLabel(this);
m_preview_label->setAlignment(Qt::AlignCenter);
m_preview_label->setMinimumSize(200, 200);
m_preview_label->setStyleSheet("QLabel { background-color: white; border: 1px solid #cccccc; }");
scroll_area->setWidget(m_preview_label);
main_layout->addWidget(scroll_area, 1);
// OK/Cancel buttons
QDialogButtonBox *button_box = new QDialogButtonBox(
QDialogButtonBox::Ok | QDialogButtonBox::Cancel,
this
);
connect(button_box, &QDialogButtonBox::accepted, this, &QDialog::accept);
connect(button_box, &QDialogButtonBox::rejected, this, &QDialog::reject);
main_layout->addWidget(button_box);
// Update preview when spinbox or DPI changes
connect(m_page_spinbox, QOverload<int>::of(&QSpinBox::valueChanged),
this, &PdfPagesDialog::updatePreview);
connect(m_dpi_combo, QOverload<int>::of(&QComboBox::currentIndexChanged),
this, &PdfPagesDialog::updatePreview);
// Render initial preview
updatePreview();
}
/**
@brief PdfPagesDialog::selectedPage
@return the selected page number (1-based)
*/
int PdfPagesDialog::selectedPage() const
{
return m_page_spinbox->value();
}
/**
@brief PdfPagesDialog::selectedDpi
@return the selected DPI value (150, 300, or 600)
*/
int PdfPagesDialog::selectedDpi() const
{
return m_dpi_combo->currentData().toInt();
}
/**
@brief PdfPagesDialog::updatePreview
Renders the currently selected page as a thumbnail and displays it
in the preview label. The preview size scales with the selected DPI
to give a visual hint of the output quality.
*/
void PdfPagesDialog::updatePreview()
{
int pageIndex = m_page_spinbox->value() - 1; // Convert to 0-based
// Calculate preview size: fixed width, proportional height
QSizeF pageSize = m_document.pagePointSize(pageIndex);
if (pageSize.isEmpty()) return;
qreal ratio = pageSize.height() / pageSize.width();
int previewHeight = qRound(PREVIEW_WIDTH * ratio);
// Render the page to QImage at preview DPI
QImage preview = m_document.render(pageIndex, QSize(PREVIEW_WIDTH, previewHeight));
if (preview.isNull()) return;
// Fill white background to handle transparent PDFs
QImage background(preview.size(), QImage::Format_ARGB32_Premultiplied);
background.fill(Qt::white);
QPainter painter(&background);
painter.drawImage(0, 0, preview);
painter.end();
m_preview_label->setPixmap(QPixmap::fromImage(background).scaled(
PREVIEW_WIDTH, previewHeight,
Qt::KeepAspectRatio, Qt::SmoothTransformation));
}
+54
View File
@@ -0,0 +1,54 @@
/*
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 PDFPAGESDIALOG_H
#define PDFPAGESDIALOG_H
#include <QDialog>
class QSpinBox;
class QComboBox;
class QLabel;
class QPdfDocument;
/**
@brief The PdfPagesDialog class
Dialog for selecting a page from a PDF document.
Displays the total page count, a live preview of the selected page,
DPI options (150/300/600), and allows the user to choose which page to import.
*/
class PdfPagesDialog : public QDialog
{
Q_OBJECT
public:
explicit PdfPagesDialog(QPdfDocument &document, QWidget *parent = nullptr);
int selectedPage() const;
int selectedDpi() const;
private slots:
void updatePreview();
private:
QPdfDocument &m_document;
QLabel *m_info_label;
QLabel *m_preview_label;
QSpinBox *m_page_spinbox;
QComboBox *m_dpi_combo;
};
#endif // PDFPAGESDIALOG_H