ImageViewer: Add option to export images from SVG.

Add a tool button showing a dialog with file name
and size for exporting images from SVG.

[ChangeLog][ImageViewer] Added option to export images from SVG.

Change-Id: I84e04dc166e70b0359eba0f19703a75b882a2bc2
Reviewed-by: Leena Miettinen <riitta-leena.miettinen@theqtcompany.com>
Reviewed-by: Alessandro Portale <alessandro.portale@theqtcompany.com>
This commit is contained in:
Friedemann Kleint
2016-03-17 16:02:20 +01:00
parent e5262fba5f
commit 44820dae13
10 changed files with 371 additions and 2 deletions

View File

@@ -0,0 +1,204 @@
/****************************************************************************
**
** Copyright (C) 2016 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 "exportdialog.h"
#include <coreplugin/coreicons.h>
#include <utils/pathchooser.h>
#include <QApplication>
#include <QDesktopWidget>
#include <QDialogButtonBox>
#include <QFileDialog>
#include <QFormLayout>
#include <QHBoxLayout>
#include <QMessageBox>
#include <QLabel>
#include <QLineEdit>
#include <QPushButton>
#include <QSpinBox>
#include <QToolButton>
#include <QVBoxLayout>
#include <QImageWriter>
#include <QDebug>
#include <QDir>
#include <QFileInfo>
#include <QMimeDatabase>
#include <QMimeType>
namespace ImageViewer {
namespace Internal {
enum { exportMinimumSize = 1, exportMaximumSize = 2000 };
static QString imageNameFilterString()
{
static QString result;
if (result.isEmpty()) {
QMimeDatabase mimeDatabase;
const QString separator = QStringLiteral(";;");
foreach (const QByteArray &mimeType, QImageWriter::supportedMimeTypes()) {
const QString filter = mimeDatabase.mimeTypeForName(QLatin1String(mimeType)).filterString();
if (!filter.isEmpty()) {
if (mimeType == QByteArrayLiteral("image/png")) {
if (!result.isEmpty())
result.prepend(separator);
result.prepend(filter);
} else {
if (!result.isEmpty())
result.append(separator);
result.append(filter);
}
}
}
}
return result;
}
ExportDialog::ExportDialog(QWidget *parent)
: QDialog(parent)
, m_pathChooser(new Utils::PathChooser(this))
, m_widthSpinBox(new QSpinBox(this))
, m_heightSpinBox(new QSpinBox(this))
, m_aspectRatio(1)
{
typedef void (QSpinBox::*QSpinBoxIntSignal)(int);
setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
QFormLayout *formLayout = new QFormLayout(this);
m_pathChooser->setMinimumWidth(QApplication::desktop()->availableGeometry(this).width() / 5);
m_pathChooser->setExpectedKind(Utils::PathChooser::SaveFile);
m_pathChooser->setPromptDialogFilter(imageNameFilterString());
formLayout->addRow(tr("File:"), m_pathChooser);
QHBoxLayout *sizeLayout = new QHBoxLayout;
m_widthSpinBox->setMinimum(exportMinimumSize);
m_widthSpinBox->setMaximum(exportMaximumSize);
connect(m_widthSpinBox, static_cast<QSpinBoxIntSignal>(&QSpinBox::valueChanged),
this, &ExportDialog::exportWidthChanged);
sizeLayout->addWidget(m_widthSpinBox);
//: Multiplication, as in 32x32
sizeLayout->addWidget(new QLabel(tr("x")));
m_heightSpinBox->setMinimum(exportMinimumSize);
m_heightSpinBox->setMaximum(exportMaximumSize);
connect(m_heightSpinBox, static_cast<QSpinBoxIntSignal>(&QSpinBox::valueChanged),
this, &ExportDialog::exportHeightChanged);
sizeLayout->addWidget(m_heightSpinBox);
QToolButton *resetButton = new QToolButton(this);
resetButton->setIcon(QIcon(QStringLiteral(":/qt-project.org/styles/commonstyle/images/refresh-32.png")));
sizeLayout->addWidget(resetButton);
sizeLayout->addStretch();
connect(resetButton, &QAbstractButton::clicked, this, &ExportDialog::resetExportSize);
formLayout->addRow(tr("Size:"), sizeLayout);
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 ExportDialog::accept()
{
if (!m_pathChooser->isValid()) {
QMessageBox::warning(this, windowTitle(), m_pathChooser->errorMessage());
return;
}
const QString fileName = exportFileName();
if (QFileInfo::exists(fileName)) {
const QString question = tr("%1 already exists.\nWould you like to overwrite it?")
.arg(QDir::toNativeSeparators(fileName));
if (QMessageBox::question(this, windowTitle(), question, QMessageBox::Yes | QMessageBox::No) != QMessageBox::Yes)
return;
}
QDialog::accept();
}
QSize ExportDialog::exportSize() const
{
return QSize(m_widthSpinBox->value(), m_heightSpinBox->value());
}
void ExportDialog::setExportSize(const QSize &size)
{
m_defaultSize = size;
const QSizeF defaultSizeF(m_defaultSize);
m_aspectRatio = defaultSizeF.width() / defaultSizeF.height();
setExportWidthBlocked(size.width());
setExportHeightBlocked(size.height());
}
void ExportDialog::resetExportSize()
{
setExportWidthBlocked(m_defaultSize.width());
setExportHeightBlocked(m_defaultSize.height());
}
void ExportDialog::setExportWidthBlocked(int width)
{
if (m_widthSpinBox->value() != width) {
const bool blockSignals = m_widthSpinBox->blockSignals(true);
m_widthSpinBox->setValue(width);
m_widthSpinBox->blockSignals(blockSignals);
}
}
void ExportDialog::setExportHeightBlocked(int height)
{
if (m_heightSpinBox->value() != height) {
const bool blockSignals = m_heightSpinBox->blockSignals(true);
m_heightSpinBox->setValue(height);
m_heightSpinBox->blockSignals(blockSignals);
}
}
void ExportDialog::exportWidthChanged(int width)
{
const bool square = m_defaultSize.width() == m_defaultSize.height();
setExportHeightBlocked(square ? width : qRound(qreal(width) / m_aspectRatio));
}
void ExportDialog::exportHeightChanged(int height)
{
const bool square = m_defaultSize.width() == m_defaultSize.height();
setExportWidthBlocked(square ? height : qRound(qreal(height) * m_aspectRatio));
}
QString ExportDialog::exportFileName() const
{
return m_pathChooser->fileName().toString();
}
void ExportDialog::setExportFileName(const QString &f)
{
m_pathChooser->setFileName(Utils::FileName::fromString(f));
}
} // namespace Internal
} // namespace ImageViewer

View File

@@ -0,0 +1,71 @@
/****************************************************************************
**
** Copyright (C) 2016 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.
**
****************************************************************************/
#ifndef EXPORTDIALOG_H
#define EXPORTDIALOG_H
#include <QDialog>
QT_FORWARD_DECLARE_CLASS(QSpinBox)
namespace Utils { class PathChooser; }
namespace ImageViewer {
namespace Internal {
class ExportDialog : public QDialog
{
Q_OBJECT
public:
explicit ExportDialog(QWidget *parent = nullptr);
QSize exportSize() const;
void setExportSize(const QSize &);
QString exportFileName() const;
void setExportFileName(const QString &);
void accept() override;
private slots:
void resetExportSize();
void exportWidthChanged(int width);
void exportHeightChanged(int height);
private:
void setExportWidthBlocked(int width);
void setExportHeightBlocked(int height);
Utils::PathChooser *m_pathChooser;
QSpinBox *m_widthSpinBox;
QSpinBox *m_heightSpinBox;
QSize m_defaultSize;
qreal m_aspectRatio;
};
} // namespace Internal
} // namespace ImageViewer
#endif // EXPORTDIALOG_H

View File

@@ -50,12 +50,29 @@
#include "imageview.h" #include "imageview.h"
#include "exportdialog.h"
#include "imageviewerfile.h" #include "imageviewerfile.h"
#include <coreplugin/messagemanager.h>
#include <utils/fileutils.h>
#include <utils/qtcassert.h>
#include <QSvgRenderer>
#include <QGraphicsSvgItem>
#include <QMessageBox>
#include <QGraphicsRectItem>
#include <QWheelEvent> #include <QWheelEvent>
#include <QMouseEvent> #include <QMouseEvent>
#include <QGraphicsRectItem> #include <QImage>
#include <QPainter>
#include <QPixmap> #include <QPixmap>
#include <QDir>
#include <QFileInfo>
#include <qmath.h> #include <qmath.h>
namespace ImageViewer { namespace ImageViewer {
@@ -137,6 +154,48 @@ void ImageView::drawBackground(QPainter *p, const QRectF &)
p->restore(); p->restore();
} }
void ImageView::exportImage()
{
#ifndef QT_NO_SVG
QGraphicsSvgItem *svgItem = qgraphicsitem_cast<QGraphicsSvgItem *>(m_imageItem);
QTC_ASSERT(svgItem, return);
const QFileInfo origFi = m_file->filePath().toFileInfo();
const QString suggestedFileName = origFi.absolutePath() + QLatin1Char('/')
+ origFi.baseName() + QStringLiteral(".png");
ExportDialog exportDialog(this);
exportDialog.setWindowTitle(tr("Export %1").arg(origFi.fileName()));
exportDialog.setExportSize(svgItem->boundingRect().size().toSize());
exportDialog.setExportFileName(suggestedFileName);
while (true) {
if (exportDialog.exec() != QDialog::Accepted)
break;
const QSize imageSize = exportDialog.exportSize();
QImage image(imageSize, QImage::Format_ARGB32);
image.fill(Qt::transparent);
QPainter painter;
painter.begin(&image);
svgItem->renderer()->render(&painter, QRectF(QPointF(), QSizeF(imageSize)));
painter.end();
const QString fileName = exportDialog.exportFileName();
if (image.save(fileName)) {
const QString message = tr("Exported \"%1\", %2x%3, %4 bytes")
.arg(QDir::toNativeSeparators(fileName)).arg(imageSize.width()).arg(imageSize.height())
.arg(QFileInfo(fileName).size());
Core::MessageManager::write(message);
break;
} else {
QMessageBox::critical(this, tr("Export Image"),
tr("Could not write file \"%1\".").arg(QDir::toNativeSeparators(fileName)));
}
}
#endif // !QT_NO_SVG
}
void ImageView::setViewBackground(bool enable) void ImageView::setViewBackground(bool enable)
{ {
m_showBackground = enable; m_showBackground = enable;

View File

@@ -74,6 +74,7 @@ signals:
void imageSizeChanged(const QSize &size); void imageSizeChanged(const QSize &size);
public slots: public slots:
void exportImage();
void setViewBackground(bool enable); void setViewBackground(bool enable);
void setViewOutline(bool enable); void setViewOutline(bool enable);
void zoomIn(); void zoomIn();

View File

@@ -100,10 +100,11 @@ void ImageViewer::ctor()
// toolbar // toolbar
d->toolbar = new QWidget(); d->toolbar = new QWidget();
d->ui_toolbar.setupUi(d->toolbar); d->ui_toolbar.setupUi(d->toolbar);
d->ui_toolbar.toolButtonExportImage->setIcon(QIcon::fromTheme(QLatin1String("document-save"),
Core::Icons::SAVEFILE.icon()));
d->ui_toolbar.toolButtonZoomIn->setIcon(Core::Icons::PLUS.icon()); d->ui_toolbar.toolButtonZoomIn->setIcon(Core::Icons::PLUS.icon());
d->ui_toolbar.toolButtonZoomOut->setIcon(Core::Icons::MINUS.icon()); d->ui_toolbar.toolButtonZoomOut->setIcon(Core::Icons::MINUS.icon());
d->ui_toolbar.toolButtonFitToScreen->setIcon(Core::Icons::ZOOM.icon()); d->ui_toolbar.toolButtonFitToScreen->setIcon(Core::Icons::ZOOM.icon());
// icons update - try to use system theme // icons update - try to use system theme
updateButtonIconByTheme(d->ui_toolbar.toolButtonZoomIn, QLatin1String("zoom-in")); updateButtonIconByTheme(d->ui_toolbar.toolButtonZoomIn, QLatin1String("zoom-in"));
updateButtonIconByTheme(d->ui_toolbar.toolButtonZoomOut, QLatin1String("zoom-out")); updateButtonIconByTheme(d->ui_toolbar.toolButtonZoomOut, QLatin1String("zoom-out"));
@@ -115,6 +116,7 @@ void ImageViewer::ctor()
// (photograph has outline - piece of paper) // (photograph has outline - piece of paper)
updateButtonIconByTheme(d->ui_toolbar.toolButtonOutline, QLatin1String("emblem-photos")); updateButtonIconByTheme(d->ui_toolbar.toolButtonOutline, QLatin1String("emblem-photos"));
d->ui_toolbar.toolButtonExportImage->setCommandId(Constants::ACTION_EXPORT_IMAGE);
d->ui_toolbar.toolButtonZoomIn->setCommandId(Constants::ACTION_ZOOM_IN); d->ui_toolbar.toolButtonZoomIn->setCommandId(Constants::ACTION_ZOOM_IN);
d->ui_toolbar.toolButtonZoomOut->setCommandId(Constants::ACTION_ZOOM_OUT); d->ui_toolbar.toolButtonZoomOut->setCommandId(Constants::ACTION_ZOOM_OUT);
d->ui_toolbar.toolButtonOriginalSize->setCommandId(Constants::ACTION_ORIGINAL_SIZE); d->ui_toolbar.toolButtonOriginalSize->setCommandId(Constants::ACTION_ORIGINAL_SIZE);
@@ -124,6 +126,8 @@ void ImageViewer::ctor()
d->ui_toolbar.toolButtonPlayPause->setCommandId(Constants::ACTION_TOGGLE_ANIMATION); d->ui_toolbar.toolButtonPlayPause->setCommandId(Constants::ACTION_TOGGLE_ANIMATION);
// connections // connections
connect(d->ui_toolbar.toolButtonExportImage, &QAbstractButton::clicked,
d->imageView, &ImageView::exportImage);
connect(d->ui_toolbar.toolButtonZoomIn, &QAbstractButton::clicked, connect(d->ui_toolbar.toolButtonZoomIn, &QAbstractButton::clicked,
d->imageView, &ImageView::zoomIn); d->imageView, &ImageView::zoomIn);
connect(d->ui_toolbar.toolButtonZoomOut, &QAbstractButton::clicked, connect(d->ui_toolbar.toolButtonZoomOut, &QAbstractButton::clicked,
@@ -150,6 +154,12 @@ void ImageViewer::ctor()
this, &ImageViewer::updatePauseAction); this, &ImageViewer::updatePauseAction);
connect(d->imageView, &ImageView::scaleFactorChanged, connect(d->imageView, &ImageView::scaleFactorChanged,
this, &ImageViewer::scaleFactorUpdate); this, &ImageViewer::scaleFactorUpdate);
connect(d->file.data(), &ImageViewerFile::openFinished,
this, [this](bool success)
{
d->ui_toolbar.toolButtonExportImage->setEnabled(success && d->file->type() == ImageViewerFile::TypeSvg);
});
} }
ImageViewer::~ImageViewer() ImageViewer::~ImageViewer()
@@ -176,6 +186,12 @@ Core::IEditor *ImageViewer::duplicate()
return other; return other;
} }
void ImageViewer::exportImage()
{
if (d->file->type() == ImageViewerFile::TypeSvg)
d->ui_toolbar.toolButtonExportImage->click();
}
void ImageViewer::imageSizeUpdated(const QSize &size) void ImageViewer::imageSizeUpdated(const QSize &size)
{ {
QString imageSizeText; QString imageSizeText;

View File

@@ -56,6 +56,7 @@ public:
IEditor *duplicate() override; IEditor *duplicate() override;
public slots: public slots:
void exportImage();
void imageSizeUpdated(const QSize &size); void imageSizeUpdated(const QSize &size);
void scaleFactorUpdate(qreal factor); void scaleFactorUpdate(qreal factor);

View File

@@ -1,6 +1,7 @@
include(../../qtcreatorplugin.pri) include(../../qtcreatorplugin.pri)
HEADERS += \ HEADERS += \
exportdialog.h \
imageviewerplugin.h \ imageviewerplugin.h \
imageviewerfactory.h \ imageviewerfactory.h \
imageviewerfile.h \ imageviewerfile.h \
@@ -9,6 +10,7 @@ HEADERS += \
imageviewerconstants.h imageviewerconstants.h
SOURCES += \ SOURCES += \
exportdialog.cpp \
imageviewerplugin.cpp \ imageviewerplugin.cpp \
imageviewerfactory.cpp \ imageviewerfactory.cpp \
imageviewerfile.cpp \ imageviewerfile.cpp \

View File

@@ -33,6 +33,7 @@ namespace Constants {
const char IMAGEVIEWER_ID[] = "Editors.ImageViewer"; const char IMAGEVIEWER_ID[] = "Editors.ImageViewer";
const char IMAGEVIEWER_DISPLAY_NAME[] = QT_TRANSLATE_NOOP("OpenWith::Editors", "Image Viewer"); const char IMAGEVIEWER_DISPLAY_NAME[] = QT_TRANSLATE_NOOP("OpenWith::Editors", "Image Viewer");
const char ACTION_EXPORT_IMAGE[] = "ImageViewer.ExportImage";
const char ACTION_ZOOM_IN[] = "ImageViewer.ZoomIn"; const char ACTION_ZOOM_IN[] = "ImageViewer.ZoomIn";
const char ACTION_ZOOM_OUT[] = "ImageViewer.ZoomOut"; const char ACTION_ZOOM_OUT[] = "ImageViewer.ZoomOut";
const char ACTION_ORIGINAL_SIZE[] = "ImageViewer.OriginalSize"; const char ACTION_ORIGINAL_SIZE[] = "ImageViewer.OriginalSize";

View File

@@ -113,6 +113,13 @@ void ImageViewerPlugin::extensionsInitialized()
if (ImageViewer *iv = currentImageViewer()) if (ImageViewer *iv = currentImageViewer())
iv->togglePlay(); iv->togglePlay();
}); });
a = registerNewAction(Constants::ACTION_EXPORT_IMAGE, tr("Export Image"),
QKeySequence());
connect(a, &QAction::triggered, this, [this]() {
if (ImageViewer *iv = currentImageViewer())
iv->exportImage();
});
} }
QAction *ImageViewerPlugin::registerNewAction(Core::Id id, QAction *ImageViewerPlugin::registerNewAction(Core::Id id,

View File

@@ -17,6 +17,13 @@
<property name="margin"> <property name="margin">
<number>0</number> <number>0</number>
</property> </property>
<item>
<widget class="Core::CommandButton" name="toolButtonExportImage">
<property name="toolTipBase">
<string>Export as Image</string>
</property>
</widget>
</item>
<item> <item>
<widget class="Core::CommandButton" name="toolButtonBackground"> <widget class="Core::CommandButton" name="toolButtonBackground">
<property name="toolTipBase"> <property name="toolTipBase">