File indexing completed on 2025-11-02 03:43:49

0001 /*
0002     File                 : ExportWorksheetDialog.cpp
0003     Project              : LabPlot
0004     Description          : export worksheet dialog
0005     --------------------------------------------------------------------
0006     SPDX-FileCopyrightText: 2011-2022 Alexander Semke <alexander.semke@web.de>
0007     SPDX-FileCopyrightText: 2021 Stefan Gerlach <stefan.gerlach@uni.kn>
0008     SPDX-License-Identifier: GPL-2.0-or-later
0009 */
0010 
0011 #include "ExportWorksheetDialog.h"
0012 #include "backend/core/Settings.h"
0013 #include "commonfrontend/worksheet/WorksheetView.h"
0014 #include "kdefrontend/GuiTools.h"
0015 #include "ui_exportworksheetwidget.h"
0016 
0017 #include <KLocalizedString>
0018 #include <KMessageBox>
0019 
0020 #include <KWindowConfig>
0021 #include <kcoreaddons_version.h>
0022 
0023 #include <QCompleter>
0024 // see https://gitlab.kitware.com/cmake/cmake/-/issues/21609
0025 #if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0))
0026 #include <QFileSystemModel>
0027 #else
0028 #include <QDirModel>
0029 #endif
0030 #include <QDialogButtonBox>
0031 #include <QFileDialog>
0032 #include <QScreen>
0033 #include <QWindow>
0034 
0035 /*!
0036     \class ExportWorksheetDialog
0037     \brief Dialog for exporting a worksheet to a file.
0038 
0039     \ingroup kdefrontend
0040 */
0041 
0042 ExportWorksheetDialog::ExportWorksheetDialog(QWidget* parent)
0043     : QDialog(parent)
0044     , ui(new Ui::ExportWorksheetWidget()) {
0045     ui->setupUi(this);
0046 
0047     auto* btnBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
0048     m_showOptionsButton = new QPushButton;
0049 
0050     connect(btnBox, &QDialogButtonBox::clicked, this, &ExportWorksheetDialog::slotButtonClicked);
0051 
0052     btnBox->addButton(m_showOptionsButton, QDialogButtonBox::ActionRole);
0053     ui->verticalLayout->addWidget(btnBox);
0054 
0055     m_okButton = btnBox->button(QDialogButtonBox::Ok);
0056     m_cancelButton = btnBox->button(QDialogButtonBox::Cancel);
0057 
0058     m_cancelButton->setToolTip(i18n("Close this dialog without exporting."));
0059 
0060 #if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0))
0061     ui->leFileName->setCompleter(new QCompleter(new QFileSystemModel, this));
0062 #else
0063     ui->leFileName->setCompleter(new QCompleter(new QDirModel, this));
0064 #endif
0065 
0066     ui->bOpen->setIcon(QIcon::fromTheme(QLatin1String("document-open")));
0067 
0068     // see WorksheetView::ExportFormat
0069     ui->cbFormat->addItem(QIcon::fromTheme(QLatin1String("application-pdf")), i18n("Portable Data Format (PDF)"));
0070     ui->cbFormat->addItem(QIcon::fromTheme(QLatin1String("image-svg+xml")), i18n("Scalable Vector Graphics (SVG)"));
0071     ui->cbFormat->insertSeparator(3);
0072     ui->cbFormat->addItem(QIcon::fromTheme(QLatin1String("image-png")), i18n("Portable Network Graphics (PNG)"));
0073     ui->cbFormat->addItem(QIcon::fromTheme(QLatin1String("image-jpeg")), i18n("Joint Photographic Experts Group (JPG)"));
0074     ui->cbFormat->addItem(QIcon::fromTheme(QLatin1String("image-bmp")), i18n("Windows Bitmap (BMP)"));
0075     ui->cbFormat->addItem(QIcon::fromTheme(QLatin1String("image-x-generic")), i18n("Portable Pixmap (PPM)"));
0076     ui->cbFormat->addItem(QIcon::fromTheme(QLatin1String("image-x-generic")), i18n("X11 Bitmap (XBM)"));
0077     ui->cbFormat->addItem(QIcon::fromTheme(QLatin1String("image-x-generic")), i18n("X11 Bitmap (XPM)"));
0078 
0079     ui->cbExportTo->addItem(i18n("File"));
0080     ui->cbExportTo->addItem(i18n("Clipboard"));
0081 
0082     ui->cbExportArea->addItem(i18n("Object's Bounding Box"));
0083     ui->cbExportArea->addItem(i18n("Current Selection"));
0084     ui->cbExportArea->addItem(i18n("Complete Worksheet"));
0085 
0086     ui->cbResolution->addItem(
0087         i18nc("%1 is the value of DPI of the current screen", "%1 (desktop)", QString::number(QApplication::primaryScreen()->physicalDotsPerInchX())));
0088     ui->cbResolution->addItem(QLatin1String("100"));
0089     ui->cbResolution->addItem(QLatin1String("150"));
0090     ui->cbResolution->addItem(QLatin1String("200"));
0091     ui->cbResolution->addItem(QLatin1String("300"));
0092     ui->cbResolution->addItem(QLatin1String("600"));
0093     ui->cbResolution->setValidator(new QIntValidator(ui->cbResolution));
0094 
0095     connect(ui->cbFormat, QOverload<int>::of(&KComboBox::currentIndexChanged), this, &ExportWorksheetDialog::formatChanged);
0096     connect(ui->cbExportTo, QOverload<int>::of(&KComboBox::currentIndexChanged), this, &ExportWorksheetDialog::exportToChanged);
0097     connect(ui->bOpen, &QPushButton::clicked, this, &ExportWorksheetDialog::selectFile);
0098     connect(ui->leFileName, &QLineEdit::textChanged, this, &ExportWorksheetDialog::fileNameChanged);
0099     connect(m_showOptionsButton, &QPushButton::clicked, this, &ExportWorksheetDialog::toggleOptions);
0100     ui->leFileName->setFocus();
0101 
0102     setWindowTitle(i18nc("@title:window", "Export Worksheet"));
0103     setWindowIcon(QIcon::fromTheme(QLatin1String("document-export-database")));
0104 
0105     // restore saved settings if available
0106     KConfigGroup conf = Settings::group(QStringLiteral("ExportWorksheetDialog"));
0107     ui->cbFormat->setCurrentIndex(conf.readEntry("Format", 0));
0108     ui->cbExportTo->setCurrentIndex(conf.readEntry("ExportTo", 0));
0109     ui->cbExportArea->setCurrentIndex(conf.readEntry("Area", 0));
0110     ui->chkExportBackground->setChecked(conf.readEntry("Background", true));
0111     ui->cbResolution->setCurrentIndex(conf.readEntry("Resolution", 0));
0112     m_showOptions = conf.readEntry("ShowOptions", true);
0113     m_showOptions ? m_showOptionsButton->setText(i18n("Hide Options")) : m_showOptionsButton->setText(i18n("Show Options"));
0114     ui->gbOptions->setVisible(m_showOptions);
0115 
0116     create(); // ensure there's a window created
0117     if (conf.exists()) {
0118         KWindowConfig::restoreWindowSize(windowHandle(), conf);
0119         resize(windowHandle()->size()); // workaround for QTBUG-40584
0120     } else
0121         resize(QSize(0, 0).expandedTo(minimumSize()));
0122 }
0123 
0124 ExportWorksheetDialog::~ExportWorksheetDialog() {
0125     // save current settings
0126     KConfigGroup conf = Settings::group(QStringLiteral("ExportWorksheetDialog"));
0127     conf.writeEntry("Format", ui->cbFormat->currentIndex());
0128     conf.writeEntry("ExportTo", ui->cbExportTo->currentIndex());
0129     conf.writeEntry("Area", ui->cbExportArea->currentIndex());
0130     conf.writeEntry("Background", ui->chkExportBackground->isChecked());
0131     conf.writeEntry("Resolution", ui->cbResolution->currentIndex());
0132     conf.writeEntry("ShowOptions", m_showOptions);
0133     KWindowConfig::saveWindowSize(windowHandle(), conf);
0134 
0135     delete ui;
0136 }
0137 
0138 /*!
0139  * sets the current project file name. If not empty, the path of the project file
0140  * is determined that is then used as the default location for the exported file.
0141  */
0142 void ExportWorksheetDialog::setProjectFileName(const QString& name) {
0143     if (name.isEmpty())
0144         return;
0145 
0146     QFileInfo fi(name);
0147     m_projectPath = fi.dir().canonicalPath();
0148 }
0149 
0150 void ExportWorksheetDialog::setFileName(const QString& name) {
0151     if (m_projectPath.isEmpty()) {
0152         // no project folder is available (yet), use the last used directory in this dialog
0153         KConfigGroup conf = Settings::group(QStringLiteral("ExportWorksheetDialog"));
0154         QString dir = conf.readEntry("LastDir", "");
0155         if (dir.isEmpty())
0156             dir = QDir::homePath();
0157         ui->leFileName->setText(dir + QLatin1String("/") + name);
0158     } else
0159         ui->leFileName->setText(m_projectPath + QLatin1String("/") + name);
0160 
0161     formatChanged(ui->cbFormat->currentIndex());
0162     exportToChanged(ui->cbExportTo->currentIndex());
0163 }
0164 
0165 QString ExportWorksheetDialog::path() const {
0166     if (ui->cbExportTo->currentIndex() == 0)
0167         return ui->leFileName->text();
0168     return {};
0169 }
0170 
0171 WorksheetView::ExportFormat ExportWorksheetDialog::exportFormat() const {
0172     int index = ui->cbFormat->currentIndex();
0173 
0174     // we have a separator in the format combobox at the 3th position -> skip it
0175     if (index > 2)
0176         index--;
0177 
0178     return WorksheetView::ExportFormat(index);
0179 }
0180 
0181 WorksheetView::ExportArea ExportWorksheetDialog::exportArea() const {
0182     return WorksheetView::ExportArea(ui->cbExportArea->currentIndex());
0183 }
0184 
0185 bool ExportWorksheetDialog::exportBackground() const {
0186     return ui->chkExportBackground->isChecked();
0187 }
0188 
0189 int ExportWorksheetDialog::exportResolution() const {
0190     if (ui->cbResolution->currentIndex() == 0)
0191         return QApplication::primaryScreen()->physicalDotsPerInchX();
0192     else
0193         return ui->cbResolution->currentText().toInt();
0194 }
0195 
0196 void ExportWorksheetDialog::slotButtonClicked(QAbstractButton* button) {
0197     if (button == m_okButton)
0198         okClicked();
0199     else if (button == m_cancelButton)
0200         reject();
0201 }
0202 
0203 // SLOTS
0204 void ExportWorksheetDialog::okClicked() {
0205     if (ui->cbExportTo->currentIndex() == 0 /*export to file*/
0206         && m_askOverwrite && QFile::exists(ui->leFileName->text())) {
0207 #if KCOREADDONS_VERSION >= QT_VERSION_CHECK(5, 100, 0)
0208         int status = KMessageBox::questionTwoActions(this,
0209                                                      i18n("The file already exists. Do you really want to overwrite it?"),
0210                                                      i18n("Export"),
0211                                                      KStandardGuiItem::overwrite(),
0212                                                      KStandardGuiItem::cancel());
0213         if (status == KMessageBox::SecondaryAction)
0214 #else
0215         int status = KMessageBox::questionYesNo(this, i18n("The file already exists. Do you really want to overwrite it?"), i18n("Export"));
0216         if (status == KMessageBox::No)
0217 #endif
0218             return;
0219     }
0220 
0221     KConfigGroup conf = Settings::group(QStringLiteral("ExportWorksheetDialog"));
0222     const auto& path = ui->leFileName->text();
0223     if (!path.isEmpty()) {
0224         QString dir = conf.readEntry("LastDir", "");
0225         int pos = path.lastIndexOf(QLatin1String("/"));
0226         if (pos != -1) {
0227             QString newDir = path.left(pos);
0228             if (newDir != dir)
0229                 conf.writeEntry("LastDir", newDir);
0230         }
0231     }
0232 
0233     accept();
0234 }
0235 
0236 /*!
0237     Shows/hides the GroupBox with export options in this dialog.
0238 */
0239 void ExportWorksheetDialog::toggleOptions() {
0240     m_showOptions = !m_showOptions;
0241     ui->gbOptions->setVisible(m_showOptions);
0242     m_showOptions ? m_showOptionsButton->setText(i18n("Hide Options")) : m_showOptionsButton->setText(i18n("Show Options"));
0243 
0244     // resize the dialog
0245     layout()->activate();
0246     resize(QSize(this->width(), 0).expandedTo(minimumSize()));
0247 }
0248 
0249 /*!
0250     opens a file dialog and lets the user select the file.
0251 */
0252 void ExportWorksheetDialog::selectFile() {
0253     KConfigGroup conf = Settings::group(QStringLiteral("ExportWorksheetDialog"));
0254     const QString dir = conf.readEntry("LastDir", "");
0255 
0256     DEBUG(Q_FUNC_INFO << ", format" << ui->cbFormat->currentIndex())
0257     QString format;
0258     int index = ui->cbFormat->currentIndex();
0259     if (index > 2) // consider separator
0260         index--;
0261 
0262     switch ((WorksheetView::ExportFormat)index) {
0263     case WorksheetView::ExportFormat::PDF:
0264         format = i18n("Portable Data Format (*.pdf *.PDF)");
0265         break;
0266     case WorksheetView::ExportFormat::SVG:
0267         format = i18n("Scalable Vector Graphics (*.svg *.SVG)");
0268         break;
0269     case WorksheetView::ExportFormat::PNG:
0270         format = i18n("Portable Network Graphics (*.png *.PNG)");
0271         break;
0272     case WorksheetView::ExportFormat::JPG:
0273         format = i18n("Joint Photographic Experts Group (*.jpg *.jpeg *.JPG *.JPEG)");
0274         break;
0275     case WorksheetView::ExportFormat::BMP:
0276         format = i18n("Windows Bitmap (*.bmp *.BMP)");
0277         break;
0278     case WorksheetView::ExportFormat::PPM:
0279         format = i18n("Portable Pixmap (*.ppm *.PPM)");
0280         break;
0281     case WorksheetView::ExportFormat::XBM:
0282         format = i18n("X11 Bitmap (*.xbm *.XBM)");
0283         break;
0284     case WorksheetView::ExportFormat::XPM:
0285         format = i18n("X11 Bitmap (*.xpm *.XPM)");
0286         break;
0287     }
0288 
0289     const QString path = QFileDialog::getSaveFileName(this, i18nc("@title:window", "Export to File"), dir, format);
0290     if (!path.isEmpty()) {
0291         // if the file is already existing, the user was already asked
0292         // in QFileDialog whether to overwrite or not.
0293         // Don't ask again when the user click on Ok-button.
0294         m_askOverwrite = false;
0295 
0296         m_initializing = true;
0297         ui->leFileName->setText(path);
0298         m_initializing = false;
0299 
0300         int pos = path.lastIndexOf(QLatin1String("/"));
0301         if (pos != -1) {
0302             const QString newDir = path.left(pos);
0303             if (newDir != dir && QDir(newDir).exists())
0304                 conf.writeEntry("LastDir", newDir);
0305         }
0306     }
0307 }
0308 
0309 /*!
0310     called when the output format was changed. Adjusts the extension for the specified file.
0311  */
0312 void ExportWorksheetDialog::formatChanged(int index) {
0313     // we have a separator in the format combobox at the 3rd position -> skip it
0314     if (index > 2)
0315         index--;
0316 
0317     QStringList extensions;
0318     // see WorksheetView::ExportFormat
0319     extensions << QLatin1String(".pdf") << QLatin1String(".svg") << QLatin1String(".png") << QLatin1String(".jpg") << QLatin1String(".bmp")
0320                << QLatin1String(".ppm") << QLatin1String(".xbm") << QLatin1String(".xpm");
0321     QString path = ui->leFileName->text();
0322     int i = path.indexOf(QLatin1Char('.'));
0323     if (i == -1)
0324         path = path + extensions.at(index);
0325     else
0326         path = path.left(i) + extensions.at(index);
0327 
0328     ui->leFileName->setText(path);
0329 
0330     // show resolution option for png format
0331     bool visible = (index == 2);
0332     ui->lResolution->setVisible(visible);
0333     ui->cbResolution->setVisible(visible);
0334 }
0335 
0336 /*!
0337     called when the target destination (file or clipboard) format was changed.
0338  */
0339 void ExportWorksheetDialog::exportToChanged(int index) {
0340     bool toFile = (index == 0);
0341     ui->lFileName->setVisible(toFile);
0342     ui->leFileName->setVisible(toFile);
0343     ui->bOpen->setVisible(toFile);
0344 
0345     if (toFile) {
0346         m_okButton->setToolTip(i18n("Export to file and close the dialog."));
0347         fileNameChanged(ui->leFileName->text()); // call this to check whether a valid file name was provided
0348     } else {
0349         m_okButton->setToolTip(i18n("Export to clipboard and close the dialog."));
0350         m_okButton->setEnabled(true);
0351     }
0352 }
0353 
0354 void ExportWorksheetDialog::fileNameChanged(const QString& name) {
0355     CONDITIONAL_LOCK_RETURN;
0356 
0357     if (name.simplified().isEmpty()) {
0358         m_okButton->setEnabled(false);
0359         return;
0360     }
0361     QString path = ui->leFileName->text();
0362     int pos = path.lastIndexOf(QLatin1String("/"));
0363     if (pos != -1) {
0364         QString dir = path.left(pos);
0365         bool invalid = !QDir(dir).exists();
0366         GuiTools::highlight(ui->leFileName, invalid);
0367         if (invalid) {
0368             m_okButton->setEnabled(false);
0369             return;
0370         }
0371     }
0372 
0373     m_askOverwrite = true;
0374     m_okButton->setEnabled(true);
0375 }