Allow scaling and converting assigned covers
This commit is contained in:
parent
955c497a52
commit
865b3501c4
|
@ -125,7 +125,8 @@ set(WIDGETS_UI_FILES
|
|||
gui/editortempoptionpage.ui
|
||||
gui/filelayout.ui
|
||||
gui/tageditorwidget.ui
|
||||
gui/dbquerywidget.ui)
|
||||
gui/dbquerywidget.ui
|
||||
gui/imageconversiondialog.ui)
|
||||
|
||||
set(TEST_HEADER_FILES)
|
||||
set(TEST_SRC_FILES tests/cli.cpp)
|
||||
|
@ -187,7 +188,8 @@ set(REQUIRED_ICONS
|
|||
tag-delete
|
||||
system-file-manager
|
||||
document-save
|
||||
view-media-lyrics)
|
||||
view-media-lyrics
|
||||
image-resize-symbolic)
|
||||
|
||||
# find c++utilities
|
||||
set(CONFIGURATION_PACKAGE_SUFFIX
|
||||
|
|
|
@ -0,0 +1,174 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>QtGui::ImageConversionDialog</class>
|
||||
<widget class="QDialog" name="QtGui::ImageConversionDialog">
|
||||
<property name="windowTitle">
|
||||
<string>Convert image</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QWidget" name="mainWidget" native="true">
|
||||
<layout class="QFormLayout" name="formLayout">
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="sizeLabel">
|
||||
<property name="text">
|
||||
<string>Size</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QWidget" name="levelWidget" native="true">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_2">
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QSpinBox" name="widthSpinBox">
|
||||
<property name="accelerated">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="suffix">
|
||||
<string> px</string>
|
||||
</property>
|
||||
<property name="minimum">
|
||||
<number>1</number>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>2048</number>
|
||||
</property>
|
||||
<property name="value">
|
||||
<number>512</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="timesLabel">
|
||||
<property name="text">
|
||||
<string>x</string>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QSpinBox" name="heightSpinBox">
|
||||
<property name="suffix">
|
||||
<string> px</string>
|
||||
</property>
|
||||
<property name="minimum">
|
||||
<number>1</number>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>2048</number>
|
||||
</property>
|
||||
<property name="value">
|
||||
<number>512</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="0">
|
||||
<widget class="QLabel" name="formatLabel">
|
||||
<property name="text">
|
||||
<string>Format</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="1">
|
||||
<widget class="QComboBox" name="formatComboBox"/>
|
||||
</item>
|
||||
<item row="4" column="0">
|
||||
<widget class="QLabel" name="aspectRatioLabel">
|
||||
<property name="text">
|
||||
<string>Aspect ratio</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="1">
|
||||
<widget class="QComboBox" name="aspectRatioComboBox"/>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QWidget" name="bottomWidget" native="true">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Maximum">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<item>
|
||||
<spacer name="horizontalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>168</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="abortPushButton">
|
||||
<property name="styleSheet">
|
||||
<string notr="true">background: none;</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Abort</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset theme="window-close">
|
||||
<normaloff>.</normaloff>.</iconset>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="confirmPushButton">
|
||||
<property name="styleSheet">
|
||||
<string notr="true">background: none;</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Confirm</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset theme="go-next">
|
||||
<normaloff>.</normaloff>.</iconset>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
|
@ -3,6 +3,7 @@
|
|||
#include "../application/settings.h"
|
||||
#include "../misc/utility.h"
|
||||
|
||||
#include "ui_imageconversiondialog.h"
|
||||
#include "ui_picturepreviewselection.h"
|
||||
|
||||
#include <tagparser/diagnostics.h>
|
||||
|
@ -14,6 +15,7 @@
|
|||
#include <tagparser/vorbis/vorbiscommentfield.h>
|
||||
|
||||
#include <qtutilities/misc/conversion.h>
|
||||
#include <qtutilities/misc/dialogutils.h>
|
||||
|
||||
#include <c++utilities/conversion/stringconversion.h>
|
||||
#include <c++utilities/misc/traits.h>
|
||||
|
@ -59,6 +61,7 @@ namespace QtGui {
|
|||
PicturePreviewSelection::PicturePreviewSelection(Tag *tag, KnownField field, QWidget *parent)
|
||||
: QWidget(parent)
|
||||
, m_ui(new Ui::PicturePreviewSelection)
|
||||
, m_imageConversionDialog(nullptr)
|
||||
, m_scene(nullptr)
|
||||
, m_textItem(nullptr)
|
||||
, m_pixmapItem(nullptr)
|
||||
|
@ -249,6 +252,71 @@ void PicturePreviewSelection::updateSizeAndMimeType(size_t fileSize, const QSize
|
|||
setToolTip(info.join(QChar('\n')));
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Assigns the specified \a image to the specified \a tagValue using the specified \a format.
|
||||
* \remarks Shows a message box if an error occurs and returns a "null" QImage.
|
||||
*/
|
||||
QImage PicturePreviewSelection::convertTagValueToImage(const TagValue &value)
|
||||
{
|
||||
QImage img;
|
||||
if (value.mimeType() == "-->") {
|
||||
const auto fileName(Utility::stringToQString(value.toString(), value.dataEncoding()));
|
||||
QFile file(fileName);
|
||||
if (file.open(QFile::ReadOnly)) {
|
||||
img = QImage::fromData(file.readAll());
|
||||
} else {
|
||||
QMessageBox::warning(this, QCoreApplication::applicationName(),
|
||||
tr("The attached image can't be found. It is supposed to be stored as external file \"%1\".").arg(fileName));
|
||||
return img;
|
||||
}
|
||||
} else if (value.dataSize() < numeric_limits<int>::max()) {
|
||||
img = QImage::fromData(reinterpret_cast<const uchar *>(value.dataPointer()), static_cast<int>(value.dataSize()));
|
||||
}
|
||||
if (img.isNull()) {
|
||||
QMessageBox::warning(this, QCoreApplication::applicationName(), tr("The attached image format is not supported."));
|
||||
}
|
||||
return img;
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Assigns the specified \a image to the specified \a tagValue using the specified \a format.
|
||||
* \remarks Shows a message box if an error occurs.
|
||||
*/
|
||||
void PicturePreviewSelection::assignImageToTagValue(const QImage &image, TagValue &tagValue, const char *format)
|
||||
{
|
||||
// set default MIME type
|
||||
QString mimeType;
|
||||
if (strcmp(format, "JPEG") == 0) {
|
||||
mimeType = QStringLiteral("image/jpeg");
|
||||
} else if (strcmp(format, "PNG") == 0) {
|
||||
mimeType = QStringLiteral("image/png");
|
||||
}
|
||||
|
||||
// save image to buffer
|
||||
QByteArray imageData;
|
||||
QBuffer buffer(&imageData);
|
||||
buffer.open(QIODevice::WriteOnly);
|
||||
if (!image.save(&buffer, format)) {
|
||||
QMessageBox::critical(this, QCoreApplication::applicationName(), tr("Unable to save image from clipboard."));
|
||||
return;
|
||||
}
|
||||
|
||||
// ask for MIME type
|
||||
if (mimeType.isEmpty()) {
|
||||
bool ok;
|
||||
mimeType
|
||||
= QInputDialog::getText(this, tr("Enter MIME type"), tr("Enter the MIME type for the pasted image."), QLineEdit::Normal, mimeType, &ok);
|
||||
if (!ok) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// assign image
|
||||
const auto mimeTypeUtf8(mimeType.toUtf8());
|
||||
tagValue.assignData(imageData.data(), static_cast<size_t>(imageData.size()), TagDataType::Picture);
|
||||
tagValue.setMimeType(mimeTypeUtf8.constData());
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Pushes the ID3v2 cover values to the specified \a tag.
|
||||
* \param tag Specifies a tag to push the values to.
|
||||
|
@ -405,39 +473,9 @@ void PicturePreviewSelection::pasteOfSelectedType(const char *format)
|
|||
return;
|
||||
}
|
||||
|
||||
// set default MIME type
|
||||
QString mimeType;
|
||||
if (strcmp(format, "JPEG") == 0) {
|
||||
mimeType = QStringLiteral("image/jpeg");
|
||||
} else if (strcmp(format, "PNG") == 0) {
|
||||
mimeType = QStringLiteral("image/png");
|
||||
}
|
||||
|
||||
// save image to buffer
|
||||
QByteArray imageData;
|
||||
QBuffer buffer(&imageData);
|
||||
buffer.open(QIODevice::WriteOnly);
|
||||
if (!image.save(&buffer, format)) {
|
||||
QMessageBox::critical(this, QCoreApplication::applicationName(), tr("Unable to save image from clipboard."));
|
||||
return;
|
||||
}
|
||||
|
||||
// ask for MIME type
|
||||
if (mimeType.isEmpty()) {
|
||||
bool ok;
|
||||
mimeType
|
||||
= QInputDialog::getText(this, tr("Enter MIME type"), tr("Enter the MIME type for the pasted image."), QLineEdit::Normal, mimeType, &ok);
|
||||
if (!ok) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// assign image
|
||||
assert(m_currentTypeIndex < m_values.size());
|
||||
TagValue &selectedCover = m_values[m_currentTypeIndex];
|
||||
const auto mimeTypeUtf8(mimeType.toUtf8());
|
||||
selectedCover.assignData(imageData.data(), static_cast<size_t>(imageData.size()), TagDataType::Picture);
|
||||
selectedCover.setMimeType(mimeTypeUtf8.constData());
|
||||
assignImageToTagValue(image, m_values[m_currentTypeIndex], format);
|
||||
|
||||
updatePreview(m_currentTypeIndex);
|
||||
}
|
||||
|
@ -503,22 +541,8 @@ void PicturePreviewSelection::displaySelected()
|
|||
}
|
||||
|
||||
// load image
|
||||
QImage img;
|
||||
if (value.mimeType() == "-->") {
|
||||
const auto fileName(Utility::stringToQString(value.toString(), value.dataEncoding()));
|
||||
QFile file(fileName);
|
||||
if (file.open(QFile::ReadOnly)) {
|
||||
img = QImage::fromData(file.readAll());
|
||||
} else {
|
||||
QMessageBox::warning(this, QCoreApplication::applicationName(),
|
||||
tr("The attached image can't be found. It is supposed to be stored as external file \"%1\".").arg(fileName));
|
||||
return;
|
||||
}
|
||||
} else if (value.dataSize() < numeric_limits<int>::max()) {
|
||||
img = QImage::fromData(reinterpret_cast<const uchar *>(value.dataPointer()), static_cast<int>(value.dataSize()));
|
||||
}
|
||||
const auto img = convertTagValueToImage(value);
|
||||
if (img.isNull()) {
|
||||
QMessageBox::warning(this, QCoreApplication::applicationName(), tr("The attached image can't be displayed."));
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -550,8 +574,10 @@ void PicturePreviewSelection::changeMimeTypeOfSelected()
|
|||
auto &selectedCover = m_values[m_currentTypeIndex];
|
||||
auto mimeType = QString::fromUtf8(selectedCover.mimeType().data());
|
||||
bool ok;
|
||||
mimeType = QInputDialog::getText(
|
||||
this, tr("Enter/confirm mime type"), tr("Confirm or enter the mime type of the selected file."), QLineEdit::Normal, mimeType, &ok);
|
||||
mimeType = QInputDialog::getText(this, tr("Enter/confirm MIME type"),
|
||||
tr("Confirm or enter the MIME type of the selected file. This merely changes the <i>assumed</i> format. <i>No</i> image format conversion "
|
||||
"done."),
|
||||
QLineEdit::Normal, mimeType, &ok);
|
||||
if (!ok) {
|
||||
return;
|
||||
}
|
||||
|
@ -559,6 +585,51 @@ void PicturePreviewSelection::changeMimeTypeOfSelected()
|
|||
updateSizeAndMimeType(m_currentFileSize, m_currentResolution, mimeType);
|
||||
}
|
||||
|
||||
void PicturePreviewSelection::convertSelected()
|
||||
{
|
||||
assert(m_currentTypeIndex < m_values.size());
|
||||
auto &selectedCover = m_values[m_currentTypeIndex];
|
||||
|
||||
// load image
|
||||
const auto img = convertTagValueToImage(selectedCover);
|
||||
if (img.isNull()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// show image conversion dialog
|
||||
if (!m_imageConversionDialog) {
|
||||
m_imageConversionDialog = new QDialog(this);
|
||||
m_imageConversionUI = make_unique<Ui::ImageConversionDialog>();
|
||||
m_imageConversionUI->setupUi(m_imageConversionDialog);
|
||||
#ifdef Q_OS_WIN32
|
||||
m_imageConversionDialog->setStyleSheet(dialogStyle());
|
||||
#endif
|
||||
m_imageConversionUI->formatComboBox->addItems({ tr("JPEG"), tr("PNG") });
|
||||
m_imageConversionUI->aspectRatioComboBox->addItems({ tr("Ignore"), tr("Keep"), tr("Keep by expanding") });
|
||||
m_imageConversionUI->aspectRatioComboBox->setCurrentIndex(1);
|
||||
connect(m_imageConversionUI->confirmPushButton, &QPushButton::clicked, m_imageConversionDialog, &QDialog::accept);
|
||||
connect(m_imageConversionUI->abortPushButton, &QPushButton::clicked, m_imageConversionDialog, &QDialog::reject);
|
||||
}
|
||||
m_imageConversionUI->widthSpinBox->setValue(img.width());
|
||||
m_imageConversionUI->heightSpinBox->setValue(img.height());
|
||||
if (m_imageConversionDialog->exec() != QDialog::Accepted) {
|
||||
return;
|
||||
}
|
||||
|
||||
// scale image
|
||||
const auto scaledImg = img.scaled(m_imageConversionUI->widthSpinBox->value(), m_imageConversionUI->heightSpinBox->value(),
|
||||
static_cast<Qt::AspectRatioMode>(m_imageConversionUI->aspectRatioComboBox->currentIndex()), Qt::SmoothTransformation);
|
||||
if (scaledImg.isNull()) {
|
||||
QMessageBox::warning(this, QCoreApplication::applicationName(), tr("Unable to scale image."));
|
||||
return;
|
||||
}
|
||||
|
||||
// assign image
|
||||
assignImageToTagValue(scaledImg, selectedCover, m_imageConversionUI->formatComboBox->currentIndex() == 0 ? "JPEG" : "PNG");
|
||||
|
||||
updatePreview(m_currentTypeIndex);
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Sets whether cover buttons are hidden.
|
||||
*/
|
||||
|
@ -760,9 +831,12 @@ void PicturePreviewSelection::showContextMenu(const QPoint &position)
|
|||
}
|
||||
#endif
|
||||
if (m_ui->extractButton->isEnabled()) {
|
||||
QAction *mimeAction = menu.addAction(tr("Change MIME-type"));
|
||||
auto *const mimeAction = menu.addAction(tr("Change MIME-type"));
|
||||
mimeAction->setIcon(QIcon::fromTheme(QStringLiteral("document-properties")));
|
||||
connect(mimeAction, &QAction::triggered, this, &PicturePreviewSelection::changeMimeTypeOfSelected);
|
||||
auto *const convertAction = menu.addAction(tr("Resize/convert assigned image"));
|
||||
convertAction->setIcon(QIcon::fromTheme(QStringLiteral("image-resize-symbolic")));
|
||||
connect(convertAction, &QAction::triggered, this, &PicturePreviewSelection::convertSelected);
|
||||
}
|
||||
menu.addSeparator();
|
||||
if (m_ui->removeButton->isEnabled()) {
|
||||
|
|
|
@ -30,7 +30,8 @@ TAGEDITOR_ENUM_CLASS PreviousValueHandling : int;
|
|||
|
||||
namespace Ui {
|
||||
class PicturePreviewSelection;
|
||||
}
|
||||
class ImageConversionDialog;
|
||||
} // namespace Ui
|
||||
|
||||
class PicturePreviewSelection : public QWidget {
|
||||
Q_OBJECT
|
||||
|
@ -60,6 +61,7 @@ public slots:
|
|||
void extractSelected();
|
||||
void displaySelected();
|
||||
void changeMimeTypeOfSelected();
|
||||
void convertSelected();
|
||||
void setCoverButtonsHidden(bool hideCoverButtons);
|
||||
|
||||
signals:
|
||||
|
@ -81,8 +83,12 @@ private slots:
|
|||
private:
|
||||
bool setup(PreviousValueHandling previousValueHandling = PreviousValueHandling::Clear);
|
||||
void updateSizeAndMimeType(std::size_t fileSize, const QSize &resolution, const QString &mimeType);
|
||||
QImage convertTagValueToImage(const TagParser::TagValue &value);
|
||||
void assignImageToTagValue(const QImage &image, TagParser::TagValue &tagValue, const char *format);
|
||||
|
||||
std::unique_ptr<Ui::PicturePreviewSelection> m_ui;
|
||||
std::unique_ptr<Ui::ImageConversionDialog> m_imageConversionUI;
|
||||
QDialog *m_imageConversionDialog;
|
||||
QGraphicsScene *m_scene;
|
||||
QGraphicsTextItem *m_textItem;
|
||||
QPixmap m_pixmap;
|
||||
|
|
Loading…
Reference in New Issue