/**************************************************************************** ** ** Copyright (C) 2018 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of Qt Creator. ** ** Commercial License Usage ** Licensees holding valid commercial Qt licenses may use this file in ** accordance with the commercial license agreement provided with the ** Software or, alternatively, in accordance with the terms contained in ** a written agreement between you and The Qt Company. For licensing terms ** and conditions see https://www.qt.io/terms-conditions. For further ** information use the contact form at https://www.qt.io/contact-us. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU ** General Public License version 3 as published by the Free Software ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT ** included in the packaging of this file. Please review the following ** information to ensure the GNU General Public License requirements will ** be met: https://www.gnu.org/licenses/gpl-3.0.html. ** ****************************************************************************/ #include "multiexportdialog.h" #include "exportdialog.h" #include "imageview.h" // ExportData #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace ImageViewer { namespace Internal { static const int standardIconSizesValues[] = {16, 24, 32, 48, 64, 128, 256}; // Helpers to convert a size specifications from QString to QSize // and vv. The format is '2x4' or '4' as shortcut for '4x4'. static QSize sizeFromString(const QStringRef &r) { if (r.isEmpty()) return {}; const int xPos = r.indexOf('x'); bool ok; const int width = xPos < 0 ? r.toInt(&ok) : r.left(xPos).toInt(&ok); if (!ok || width <= 0) return {}; if (xPos < 0) return {width, width}; const int height = r.mid(xPos + 1).toInt(&ok); if (!ok || height <= 0) return {}; return {width, height}; } static void appendSizeSpec(const QSize &size, QString *target) { target->append(QString::number(size.width())); if (size.width() != size.height()) { target->append('x'); target->append(QString::number(size.height())); } } static inline QString sizeToString(const QSize &size) { QString result; appendSizeSpec(size, &result); return result; } static QString sizesToString(const QVector &sizes) { QString result; for (int i = 0, size = sizes.size(); i < size; ++i) { if (i) result.append(','); appendSizeSpec(sizes.at(i), &result); } return result; } static QVector stringToSizes(const QString &s) { QVector result; const QString trimmed = s.trimmed(); const QVector &sizes = trimmed.splitRef(',', Utils::SkipEmptyParts); result.reserve(sizes.size()); for (const QStringRef &sizeSpec : sizes) { const QSize size = sizeFromString(sizeSpec); if (!size.isValid() || size.isEmpty()) return {}; else result.append(size); } return result; } static QString fileNameForSize(QString pattern, const QSize &s) { pattern.replace("%1", QString::number(s.width())); pattern.replace("%2", QString::number(s.height())); return pattern; } // Helpers for writing/reading the user-specified size specifications // from/to the settings. static inline QString settingsGroup() { return QStringLiteral("ExportSvgSizes"); } static QVector readSettings(const QSize &size) { QVector result; QSettings *settings = Core::ICore::settings(); settings->beginGroup(settingsGroup()); const QStringList keys = settings->allKeys(); const int idx = keys.indexOf(sizeToString(size)); if (idx >= 0) result = stringToSizes(settings->value(keys.at(idx)).toString()); settings->endGroup(); return result; } static void writeSettings(const QSize &size, const QString &sizeSpec) { QSettings *settings = Core::ICore::settings(); settings->beginGroup(settingsGroup()); const QString spec = sizeToString(size); settings->setValue(spec, QVariant(sizeSpec)); // Limit the number of sizes to 10. Remove the // first element unless it is the newly added spec. QStringList keys = settings->allKeys(); while (keys.size() > 10) { const int existingIndex = keys.indexOf(spec); const int removeIndex = existingIndex == 0 ? 1 : 0; settings->remove(keys.takeAt(removeIndex)); } settings->endGroup(); } QVector MultiExportDialog::standardIconSizes() { QVector result; const int size = int(sizeof(standardIconSizesValues) / sizeof(standardIconSizesValues[0])); result.reserve(size); for (int standardIconSizesValue : standardIconSizesValues) result.append(QSize(standardIconSizesValue, standardIconSizesValue)); return result; } // --- MultiExportDialog MultiExportDialog::MultiExportDialog(QWidget *parent) : QDialog(parent) , m_pathChooser(new Utils::PathChooser(this)) , m_sizesLineEdit(new QLineEdit) { auto formLayout = new QFormLayout(this); m_pathChooser->setMinimumWidth(QApplication::desktop()->availableGeometry(this).width() / 5); m_pathChooser->setExpectedKind(Utils::PathChooser::SaveFile); m_pathChooser->setPromptDialogFilter(ExportDialog::imageNameFilterString()); const QString pathChooserToolTip = tr("Enter a file name containing place holders %1 " "which will be replaced by the width and height of the image, respectively.") .arg("%1, %2"); m_pathChooser->setToolTip(pathChooserToolTip); QLabel *pathChooserLabel = new QLabel(tr("File:")); pathChooserLabel->setToolTip(pathChooserToolTip); formLayout->addRow(pathChooserLabel, m_pathChooser); auto sizeEditButton = new QToolButton; sizeEditButton->setFocusPolicy(Qt::NoFocus); sizeEditButton->setIcon(Utils::Icons::ARROW_DOWN.icon()); auto sizeEditMenu = new QMenu(this); sizeEditMenu->addAction(tr("Clear"), m_sizesLineEdit, &QLineEdit::clear); sizeEditMenu->addAction(tr("Set Standard Icon Sizes"), this, &MultiExportDialog::setStandardIconSizes); sizeEditMenu->addAction(tr("Generate Sizes"), this, &MultiExportDialog::setGeneratedSizes); sizeEditButton->setMenu(sizeEditMenu); sizeEditButton->setPopupMode(QToolButton::InstantPopup); const QString sizesToolTip = tr("A comma-separated list of size specifications of the form \"x\"."); auto sizesLabel = new QLabel(tr("Sizes:")); sizesLabel->setToolTip(sizesToolTip); formLayout->addRow(sizesLabel, m_sizesLineEdit); m_sizesLineEdit->setToolTip(sizesToolTip); auto optionsAction = new QWidgetAction(this); optionsAction->setDefaultWidget(sizeEditButton); m_sizesLineEdit->addAction(optionsAction, QLineEdit::TrailingPosition); QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this); connect(buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept); connect(buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject); formLayout->addRow(buttonBox); } void MultiExportDialog::setSizes(const QVector &s) { m_sizesLineEdit->setText(sizesToString(s)); } QVector MultiExportDialog::sizes() const { return stringToSizes(sizesSpecification()); } void MultiExportDialog::setStandardIconSizes() { setSizes(standardIconSizes()); } void MultiExportDialog::setGeneratedSizes() { QVector sizes; if (m_svgSize.width() >= 16) sizes.append(m_svgSize / 2); sizes.append(m_svgSize); for (int factor = 2; sizes.size() < 4; factor *= 2) sizes.append(m_svgSize * factor); setSizes(sizes); } void MultiExportDialog::suggestSizes() { const QVector settingsEntries = readSettings(m_svgSize); if (!settingsEntries.isEmpty()) setSizes(settingsEntries); else if (m_svgSize.width() == m_svgSize.height()) // Square: Assume this is an icon setStandardIconSizes(); else setGeneratedSizes(); } QVector MultiExportDialog::exportData() const { const QVector sizeList = sizes(); const QString pattern = exportFileName(); QVector result; result.reserve(sizeList.size()); for (const QSize &s : sizeList) result.append({fileNameForSize(pattern, s), s}); return result; } QString MultiExportDialog::sizesSpecification() const { return m_sizesLineEdit->text().trimmed(); } void MultiExportDialog::accept() { if (!m_pathChooser->isValid()) { QMessageBox::warning(this, windowTitle(), m_pathChooser->errorMessage()); return; } const QString &sizeSpec = sizesSpecification(); if (sizeSpec.isEmpty()) { QMessageBox::warning(this, windowTitle(), tr("Please specify some sizes.")); return; } const QVector &data = exportData(); if (data.isEmpty()) { QMessageBox::warning(this, windowTitle(), tr("Invalid size specification: %1").arg(sizeSpec)); return; } if (data.size() > 1 && data.at(0).fileName == data.at(1).fileName) { QMessageBox::warning(this, windowTitle(), tr("The file name must contain one of the placeholders %1, %2.") .arg(QString("%1"), QString("%2"))); return; } writeSettings(m_svgSize, sizeSpec); QStringList existingFiles; for (const ExportData &d : data) { if (QFileInfo::exists(d.fileName)) existingFiles.append(d.fileName); } if (!existingFiles.isEmpty()) { const QString message = existingFiles.size() == 1 ? tr("The file %1 already exists.\nWould you like to overwrite it?") .arg(QDir::toNativeSeparators(existingFiles.constFirst())) : tr("The files %1 already exist.\nWould you like to overwrite them?") .arg(QDir::toNativeSeparators(existingFiles.join(", "))); QMessageBox messageBox(QMessageBox::Question, windowTitle(), message, QMessageBox::Yes | QMessageBox::No, this); if (messageBox.exec() != QMessageBox::Yes) return; } QDialog::accept(); } QString MultiExportDialog::exportFileName() const { return m_pathChooser->filePath().toString(); } void MultiExportDialog::setExportFileName(QString f) { const int lastDot = f.lastIndexOf('.'); if (lastDot != -1) f.insert(lastDot, "-%1"); m_pathChooser->setFilePath(Utils::FilePath::fromString(f)); } } // namespace Internal } // namespace ImageViewer