diff --git a/src/crystalx.cpp b/src/crystalx.cpp index 95ec692..c02f65f 100644 --- a/src/crystalx.cpp +++ b/src/crystalx.cpp @@ -16,6 +16,7 @@ #include "crystalx.h" #include "dialoghtml.h" #include "elementdata.h" +#include "exportdialog.h" #include "infodocuments.h" #include "isosurface_calculator.h" #include "mathconstants.h" @@ -877,10 +878,8 @@ void Crystalx::loadExternalFileData(QString filename) { showStatusMessage( QString("Loading crystal clear output from %1").arg(filename)); project->loadCrystalClearJson(filename); - } - else if(filename.endsWith("surface.json")) { - showStatusMessage( - QString("Loading crystal surface from %1").arg(filename)); + } else if (filename.endsWith("surface.json")) { + showStatusMessage(QString("Loading crystal surface from %1").arg(filename)); project->loadCrystalClearSurfaceJson(filename); } else if (extension == CIF_EXTENSION || extension == CIF2_EXTENSION) { processCif(filename); @@ -1441,29 +1440,38 @@ void Crystalx::exportAs() { if (!project->currentScene()) return; - QString filter = "Portable Network Graphics (*.png);;POV-ray (*.pov)"; QFileInfo fi(project->currentScene()->title()); QString suggestedFilename = fi.baseName() + ".png"; - QString filename = QFileDialog::getSaveFileName(0, tr("Export graphics"), - suggestedFilename, filter); - bool success = false; - if (filename.isEmpty()) - return; + ExportDialog dialog(this); + QImage previewImage = glWindow->renderToImage(1); + dialog.updateImage(previewImage); + dialog.updateFilePath(suggestedFilename); + dialog.updateBackgroundColor(glWindow->backgroundColor()); - if (filename.toLower().endsWith(".png")) { - QImage img = glWindow->renderToImage(1); - success = img.save(filename); - } else { - QFile outputFile(filename); - outputFile.open(QIODevice::WriteOnly); - if (outputFile.isOpen()) { - QTextStream outStream(&outputFile); - success = glWindow->renderToPovRay(outStream); + QString filename{""}; + bool success = false; + if (dialog.exec() == QDialog::Accepted) { + // The user clicked OK + filename = dialog.currentFilePath(); + int scaleFactor = dialog.currentResolutionScale(); + QColor backgroundColor = dialog.currentBackgroundColor(); + + if (filename.toLower().endsWith(".png")) { + QImage img = glWindow->exportToImage(scaleFactor, backgroundColor); + qDebug() << "Exporting image with scale factor" << scaleFactor << "resolution" << img.size(); + success = img.save(filename); + } else { + QFile outputFile(filename); + outputFile.open(QIODevice::WriteOnly); + if (outputFile.isOpen()) { + QTextStream outStream(&outputFile); + success = glWindow->renderToPovRay(outStream); + } } } - if (success) { + if (!filename.isEmpty()) { showStatusMessage("Saved current graphics state to " + filename); } } @@ -1970,11 +1978,14 @@ void Crystalx::showEnergyFrameworkDialog() { Scene *scene = project->currentScene(); if (!scene) return; - auto * structure = scene->chemicalStructure(); - if(!structure) return; - auto * interactions = structure->pairInteractions(); - if(!interactions) return; - if(!interactions->haveInteractions()) return; + auto *structure = scene->chemicalStructure(); + if (!structure) + return; + auto *interactions = structure->pairInteractions(); + if (!interactions) + return; + if (!interactions->haveInteractions()) + return; if (childPropertyController) { childPropertyController->setCurrentPairInteractions(interactions); diff --git a/src/dialogs/CMakeLists.txt b/src/dialogs/CMakeLists.txt index 62bd76c..8bae0ac 100644 --- a/src/dialogs/CMakeLists.txt +++ b/src/dialogs/CMakeLists.txt @@ -5,6 +5,7 @@ qt_wrap_ui(DIALOG_UI_HEADERS "${CMAKE_CURRENT_SOURCE_DIR}/closecontactsdialog.ui" "${CMAKE_CURRENT_SOURCE_DIR}/depthfadingandclippingdialog.ui" "${CMAKE_CURRENT_SOURCE_DIR}/elementeditor.ui" + "${CMAKE_CURRENT_SOURCE_DIR}/exportdialog.ui" "${CMAKE_CURRENT_SOURCE_DIR}/energycalculationdialog.ui" "${CMAKE_CURRENT_SOURCE_DIR}/fingerprintoptions.ui" "${CMAKE_CURRENT_SOURCE_DIR}/fileeditor.ui" @@ -27,6 +28,7 @@ qt_add_library(cx_dialogs STATIC "${CMAKE_CURRENT_SOURCE_DIR}/depthfadingandclippingdialog.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/elementeditor.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/energycalculationdialog.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/exportdialog.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/fingerprintoptions.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/fingerprintplot.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/fingerprintwindow.cpp" diff --git a/src/dialogs/exportdialog.cpp b/src/dialogs/exportdialog.cpp new file mode 100644 index 0000000..a0990aa --- /dev/null +++ b/src/dialogs/exportdialog.cpp @@ -0,0 +1,158 @@ +#include "exportdialog.h" +#include "ui_exportdialog.h" +#include +#include +#include +#include +#include + +ExportDialog::ExportDialog(QWidget *parent) + : QDialog(parent), ui(new Ui::ExportDialog) { + ui->setupUi(this); + QStringList resolutionOptions{"1x", "2x", "3x", "4x"}; + ui->resolutionScaleComboBox->addItems(resolutionOptions); + ui->resolutionScaleComboBox->setCurrentText("1x"); + initConnections(); +} + +ExportDialog::~ExportDialog() { delete ui; } + +void ExportDialog::initConnections() { + connect(ui->destinationBrowseButton, &QPushButton::clicked, this, + &ExportDialog::selectFile); + connect(ui->buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept); + connect(ui->buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject); + connect(ui->resolutionScaleComboBox, &QComboBox::currentIndexChanged, this, + &ExportDialog::updateResolutionLabel); + connect(ui->backgroundColorToolButton, &QAbstractButton::clicked, [&]() { + QColor color = QColorDialog::getColor(currentBackgroundColor(), this, + "Set Background Color for Export", + QColorDialog::ShowAlphaChannel); + if (color.isValid()) { + updateBackgroundColor(color); + } + }); +} + +int ExportDialog::currentResolutionScale() const { + QString currentText = ui->resolutionScaleComboBox->currentText(); + // Remove the 'x' at the end of the string + currentText = currentText.remove(currentText.length() - 1, 1); + bool conversionOk{false}; + int scale = currentText.toInt(&conversionOk); + + if (!conversionOk) { + qWarning() << "Failed to convert resolution scale text to float: " + << currentText; + return 1; // Default to 1x if conversion fails + } + return scale; +} + +void ExportDialog::updateResolutionLabel() { + int scale = currentResolutionScale(); + if (m_currentPixmap.isNull()) { + ui->resolutionLabel->setText("N/A"); + return; + } + QSize originalSize = m_currentPixmap.size(); + + int newWidth = originalSize.width() * scale; + int newHeight = originalSize.height() * scale; + + QString resolutionText = QString("%1 x %2 px").arg(newWidth).arg(newHeight); + + ui->resolutionLabel->setText(resolutionText); +} + +void ExportDialog::updateFilePath(QString path) { + m_currentFilePath = path; + ui->destinationLineEdit->setText(path); +} + +QColor ExportDialog::currentBackgroundColor() const { + return m_currentBackgroundColor; +} + +void ExportDialog::updateBackgroundColor(const QColor &color) { + m_currentBackgroundColor = color; + auto *button = ui->backgroundColorToolButton; + QPixmap pixmap = QPixmap(button->iconSize()); + pixmap.fill(color); + button->setIcon(QIcon(pixmap)); +} + +void ExportDialog::selectFile() { + QString filter = "Portable Network Graphics (*.png);; POV-ray (*.pov)"; + QString filePath = QFileDialog::getSaveFileName(this, tr("Export graphics"), + m_currentFilePath, filter); + + if (!filePath.isEmpty()) { + m_currentFilePath = filePath; + updateFilePath(filePath); + updatePreview(); + } +} + +void ExportDialog::updateImage(const QImage &image) { + bool success = m_currentPixmap.convertFromImage(image); + qDebug() << "Loaded pixmap from image"; + updatePreview(); +} + +void ExportDialog::resizeEvent(QResizeEvent *event) { + QDialog::resizeEvent(event); + qDebug() << "Dialog resized to:" << event->size(); + updatePreview(); +} + +void ExportDialog::updatePreview() { + auto *label = ui->pixmapDisplayLabel; + + qDebug() << "Dialog size:" << this->size(); + qDebug() << "Label size:" << label->size(); + qDebug() << "Label minimum size:" << label->minimumSize(); + qDebug() << "Label maximum size:" << label->maximumSize(); + qDebug() << "Label size policy:" << label->sizePolicy().horizontalPolicy() + << label->sizePolicy().verticalPolicy(); + + if (m_currentPixmap.isNull()) { + qDebug() << "Current pixmap is null"; + label->setText("No image to preview"); + return; + } + + QSize labelSize = label->size(); + QPixmap scaledPixmap = m_currentPixmap.scaled(labelSize, Qt::KeepAspectRatio, + Qt::SmoothTransformation); + + QPixmap background(labelSize); + background.fill(QColor::fromRgb(255, 255, 255, 0)); + + // Create a painter to draw on the background + QPainter painter(&background); + + // Calculate position to center the scaled pixmap + int x = (labelSize.width() - scaledPixmap.width()) / 2; + int y = (labelSize.height() - scaledPixmap.height()) / 2; + + // Draw the scaled pixmap onto the background + painter.drawPixmap(x, y, scaledPixmap); + + // Set the final composited pixmap to the label + label->setPixmap(background); + qDebug() << "Original pixmap size:" << m_currentPixmap.size(); + qDebug() << "Scaled pixmap size:" << scaledPixmap.size(); + updateResolutionLabel(); +} + +void ExportDialog::accept() { + if (m_currentFilePath.isEmpty()) { + QMessageBox::warning(this, "Warning", "Please select a destination file."); + return; // Don't accept yet + } + + QDialog::accept(); +} + +void ExportDialog::reject() { QDialog::reject(); } diff --git a/src/dialogs/exportdialog.h b/src/dialogs/exportdialog.h new file mode 100644 index 0000000..f3512ae --- /dev/null +++ b/src/dialogs/exportdialog.h @@ -0,0 +1,42 @@ +#pragma once +#include +#include + +namespace Ui { +class ExportDialog; +} + +class ExportDialog : public QDialog { + Q_OBJECT + +public: + explicit ExportDialog(QWidget *parent = nullptr); + ~ExportDialog(); + + QString currentFilePath() const { return m_currentFilePath; } + int currentResolutionScale() const; + QColor currentBackgroundColor() const; + +public slots: + void updateImage(const QImage &); + void updateFilePath(QString); + void updateBackgroundColor(const QColor &); + + void accept() override; + void reject() override; + +private slots: + void updateResolutionLabel(); + void selectFile(); + void updatePreview(); + +protected: + void resizeEvent(QResizeEvent *event) override; + +private: + QString m_currentFilePath{"destination.png"}; + QPixmap m_currentPixmap; + QColor m_currentBackgroundColor{Qt::white}; + void initConnections(); + Ui::ExportDialog *ui; +}; diff --git a/src/dialogs/exportdialog.ui b/src/dialogs/exportdialog.ui new file mode 100644 index 0000000..869f79b --- /dev/null +++ b/src/dialogs/exportdialog.ui @@ -0,0 +1,177 @@ + + + ExportDialog + + + Qt::ApplicationModal + + + + 0 + 0 + 567 + 374 + + + + Dialog + + + true + + + true + + + + + + + 0 + 0 + + + + + 128 + 128 + + + + + + + Qt::AlignCenter + + + + + + + + 0 + 0 + + + + Settings + + + + + + Resolution Scale Factor + + + + + + + + 0 + 0 + + + + + + + + 0x0 + + + + + + + + + + + + + + Background Color + + + + + + + + + + + + + Destination + + + + + + + + + + + 0 + 0 + + + + Browse + + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + buttonBox + accepted() + ExportDialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + ExportDialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/src/glwindow.cpp b/src/glwindow.cpp index fe713a2..e9bf55a 100644 --- a/src/glwindow.cpp +++ b/src/glwindow.cpp @@ -350,6 +350,36 @@ void GLWindow::paintGL() { m_postprocessShader->release(); } +QImage GLWindow::exportToImage(int scaleFactor, const QColor &background) { + makeCurrent(); + int w = width() * scaleFactor; + int h = height() * scaleFactor; + glViewport(0, 0, w, h); + + setModelView(); + QOpenGLFramebufferObject fbo(w, h, + QOpenGLFramebufferObject::CombinedDepthStencil); + + fbo.bind(); + if (enableDepthTest) { + glEnable(GL_DEPTH_TEST); + } + glClearColor(background.redF(), background.greenF(), background.blueF(), background.alphaF()); + glClearDepth(0); + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + glClearDepth(0); + drawScene(false); + fbo.release(); + + QImage result(fbo.toImage()); + const QColor &color = scene->backgroundColor(); + glClearColor(color.redF(), color.greenF(), color.blueF(), color.alphaF()); + glClearDepth(0); + + doneCurrent(); + return result; +} + QImage GLWindow::renderToImage(int scaleFactor, bool for_picking) { makeCurrent(); int w = width() * scaleFactor; diff --git a/src/glwindow.h b/src/glwindow.h index 2521fd6..a04bc0c 100644 --- a/src/glwindow.h +++ b/src/glwindow.h @@ -54,6 +54,7 @@ class GLWindow : public QOpenGLWidget, protected QOpenGLExtraFunctions { Scene *currentScene() const { return scene; } QImage renderToImage(int scaleFactor, bool for_picking = false); + QImage exportToImage(int scaleFactor = 1, const QColor & background = Qt::white); bool renderToPovRay(QTextStream &); public slots: