From 82dab12acd25a71e350b43500a3f98061cfd135e Mon Sep 17 00:00:00 2001 From: Ville Voutilainen Date: Wed, 19 Feb 2020 12:02:44 +0200 Subject: [PATCH] Android: make icon removal actually work Also adjusts icon preview sizes and adds a master icon from which the other icons are generated. Task-number: QTCREATORBUG-23283 Change-Id: I21c3d11f9b5d4d815dc6d9ad7c2363b67767d03a Reviewed-by: Alessandro Portale --- .../android/androidmanifesteditorwidget.cpp | 279 +++++++++++++----- .../android/androidmanifesteditorwidget.h | 19 +- 2 files changed, 224 insertions(+), 74 deletions(-) diff --git a/src/plugins/android/androidmanifesteditorwidget.cpp b/src/plugins/android/androidmanifesteditorwidget.cpp index 6f0bff08bcb..bc80a84ba75 100644 --- a/src/plugins/android/androidmanifesteditorwidget.cpp +++ b/src/plugins/android/androidmanifesteditorwidget.cpp @@ -49,32 +49,39 @@ #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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include +#include +#include +#include #include #include + +namespace { +static Q_LOGGING_CATEGORY(androidManifestEditorLog, "qtc.android.manifestEditor", QtWarningMsg) +} + using namespace ProjectExplorer; using namespace Android; using namespace Android::Internal; @@ -237,22 +244,44 @@ void AndroidManifestEditorWidget::initializePage() createDPIButton(iconLayout, applicationGroupBox, - m_lIconButton, m_lIconClearButton, - tr("Low DPI icon"), tr("Select low DPI icon.")); + m_masterIconButton, iconSize(LowDPI), + tr("Master icon"), tr("Select master icon.")); + + m_masterIconButton->setIcon(QIcon::fromTheme(QLatin1String("document-open"), Utils::Icons::OPENFILE.icon())); + + iconLayout->addStretch(1); + + QFrame* line = new QFrame(); + line->setFrameShape(QFrame::VLine); + line->setFrameShadow(QFrame::Sunken); + iconLayout->addWidget(line); iconLayout->addStretch(1); createDPIButton(iconLayout, applicationGroupBox, - m_mIconButton, m_mIconClearButton, - tr("Medium DPI icon"), tr("Select medium DPI icon.")); + m_lIconButton, iconSize(LowDPI), + tr("Low DPI icon"), tr("Select low DPI icon."), + &m_lIconClearButton, + &m_lIconScaleWarningLabel); iconLayout->addStretch(1); createDPIButton(iconLayout, applicationGroupBox, - m_hIconButton, m_hIconClearButton, - tr("High DPI icon"), tr("Select high DPI icon.")); + m_mIconButton, iconSize(MediumDPI), + tr("Medium DPI icon"), tr("Select medium DPI icon."), + &m_mIconClearButton, + &m_mIconScaleWarningLabel); + + iconLayout->addStretch(1); + + createDPIButton(iconLayout, + applicationGroupBox, + m_hIconButton, iconSize(HighDPI), + tr("High DPI icon"), tr("Select high DPI icon."), + &m_hIconClearButton, + &m_hIconScaleWarningLabel); iconLayout->addStretch(6); @@ -269,6 +298,8 @@ void AndroidManifestEditorWidget::initializePage() connect(m_targetLineEdit, &QComboBox::currentTextChanged, this, setDirtyFunc); + connect(m_masterIconButton, &QAbstractButton::clicked, + this, &AndroidManifestEditorWidget::setMasterIcon); connect(m_lIconButton, &QAbstractButton::clicked, this, &AndroidManifestEditorWidget::setLDPIIcon); connect(m_mIconButton, &QAbstractButton::clicked, @@ -549,10 +580,7 @@ void AndroidManifestEditorWidget::setDirty(bool dirty) bool AndroidManifestEditorWidget::isModified() const { - return m_dirty - || !m_hIconPath.isEmpty() - || !m_mIconPath.isEmpty() - || !m_lIconPath.isEmpty(); + return m_dirty; } AndroidManifestEditorWidget::EditorPage AndroidManifestEditorWidget::activePage() const @@ -592,18 +620,10 @@ void AndroidManifestEditorWidget::preSave() syncToEditor(); QString baseDir = m_textEditorWidget->textDocument()->filePath().toFileInfo().absolutePath(); - if (!m_lIconPath.isEmpty()) { - copyIcon(LowDPI, baseDir, m_lIconPath); - m_lIconPath.clear(); - } - if (!m_mIconPath.isEmpty()) { - copyIcon(MediumDPI, baseDir, m_mIconPath); - m_mIconPath.clear(); - } - if (!m_hIconPath.isEmpty()) { - copyIcon(HighDPI, baseDir, m_hIconPath); - m_hIconPath.clear(); - } + copyIcon(LowDPI, baseDir, m_lIconPath); + copyIcon(MediumDPI, baseDir, m_mIconPath); + copyIcon(HighDPI, baseDir, m_hIconPath); + // no need to emit changed() since this is called as part of saving updateInfoBar(); @@ -795,9 +815,9 @@ void AndroidManifestEditorWidget::syncToWidgets(const QDomDocument &doc) m_lIconButton->setIcon(icon(baseDir, LowDPI)); m_mIconButton->setIcon(icon(baseDir, MediumDPI)); m_hIconButton->setIcon(icon(baseDir, HighDPI)); - m_lIconPath.clear(); - m_mIconPath.clear(); - m_hIconPath.clear(); + m_lIconPath = baseDir + iconPath(LowDPI); + m_mIconPath = baseDir + iconPath(MediumDPI); + m_hIconPath = baseDir + iconPath(HighDPI); disconnect(m_defaultPermissonsCheckBox, &QCheckBox::stateChanged, this, &AndroidManifestEditorWidget::defaultPermissionOrFeatureCheckBoxClicked); @@ -999,15 +1019,17 @@ void AndroidManifestEditorWidget::parseApplication(QXmlStreamReader &reader, QXm QXmlStreamAttributes attributes = reader.attributes(); QStringList keys = {QLatin1String("android:label")}; QStringList values = {m_appNameLineEdit->text()}; + QStringList remove; bool ensureIconAttribute = !m_lIconPath.isEmpty() || !m_mIconPath.isEmpty() || !m_hIconPath.isEmpty(); if (ensureIconAttribute) { keys << QLatin1String("android:icon"); values << QLatin1String("@drawable/icon"); - } + } else + remove << QLatin1String("android:icon"); - QXmlStreamAttributes result = modifyXmlStreamAttributes(attributes, keys, values); + QXmlStreamAttributes result = modifyXmlStreamAttributes(attributes, keys, values, remove); writer.writeAttributes(result); reader.readNext(); @@ -1230,6 +1252,34 @@ QString AndroidManifestEditorWidget::iconPath(IconDPI dpi) return {}; } +QSize AndroidManifestEditorWidget::iconSize(IconDPI dpi) +{ + switch (dpi) { + case HighDPI: + return QSize(72, 72); + case MediumDPI: + return QSize(48, 48); + case LowDPI: + return QSize(32, 32); + } + return QSize(72, 72); +} + +void AndroidManifestEditorWidget::updateIconPath(const QString &newPath, IconDPI dpi) +{ + switch (dpi) { + case HighDPI: + m_hIconPath = newPath; + break; + case MediumDPI: + m_mIconPath = newPath; + break; + case LowDPI: + m_lIconPath = newPath; + break; + } +} + QIcon AndroidManifestEditorWidget::icon(const QString &baseDir, IconDPI dpi) { @@ -1250,90 +1300,177 @@ QIcon AndroidManifestEditorWidget::icon(const QString &baseDir, IconDPI dpi) void AndroidManifestEditorWidget::copyIcon(IconDPI dpi, const QString &baseDir, const QString &filePath) { - if (!QFileInfo::exists(filePath)) - return; - const QString targetPath = baseDir + iconPath(dpi); - QFile::remove(targetPath); - QDir dir; - dir.mkpath(QFileInfo(targetPath).absolutePath()); - QFile::copy(filePath, targetPath); + if (targetPath.isEmpty()) { + qCDebug(androidManifestEditorLog) << "Icon target path empty, cannot copy icon."; + return; + } + QFileInfo targetFile(targetPath); + if (filePath == targetPath) + return; + removeIcon(dpi, baseDir); + QImage original(filePath); + if (!targetPath.isEmpty() && !original.isNull()) { + QDir dir; + dir.mkpath(QFileInfo(targetPath).absolutePath()); + QSize targetSize = iconSize(dpi); + QImage scaled = original.scaled(targetSize.width(), targetSize.height(), + Qt::KeepAspectRatio, Qt::SmoothTransformation); + toggleIconScaleWarning(dpi, scaled.width() > original.width() || scaled.height() > original.height()); + scaled.save(targetPath); + updateIconPath(targetPath, dpi); + } +} + +void AndroidManifestEditorWidget::removeIcon(IconDPI dpi, const QString &baseDir) +{ + const QString targetPath = baseDir + iconPath(dpi); + if (targetPath.isEmpty()) { + qCDebug(androidManifestEditorLog) << "Icon target path empty, cannot remove icon."; + return; + } + QFileInfo targetFile(targetPath); + if (targetFile.exists()) { + QDir rmRf(targetFile.absoluteDir()); + rmRf.removeRecursively(); + } + toggleIconScaleWarning(dpi, false); +} + +void AndroidManifestEditorWidget::toggleIconScaleWarning(IconDPI dpi, bool visible) +{ + switch (dpi) { + case HighDPI: + m_hIconScaleWarningLabel->setVisible(visible); + break; + case MediumDPI: + m_mIconScaleWarningLabel->setVisible(visible); + break; + case LowDPI: + m_lIconScaleWarningLabel->setVisible(visible); + break; + } +} + +const auto fileDialogIconFiles = QWidget::tr("Images (*.png *.jpg *.webp *.svg)"); + +void AndroidManifestEditorWidget::setMasterIcon() +{ + QString file = QFileDialog::getOpenFileName(this, tr("Choose Master Icon"), QDir::homePath(), fileDialogIconFiles); + if (file.isEmpty()) + return; + QString baseDir = m_textEditorWidget->textDocument()->filePath().toFileInfo().absolutePath(); + copyIcon(LowDPI, baseDir, file); + copyIcon(MediumDPI, baseDir, file); + copyIcon(HighDPI, baseDir, file); + m_lIconButton->setIcon(icon(baseDir, LowDPI)); + m_mIconButton->setIcon(icon(baseDir, MediumDPI)); + m_hIconButton->setIcon(icon(baseDir, HighDPI)); } void AndroidManifestEditorWidget::setLDPIIcon() { - QString file = QFileDialog::getOpenFileName(this, tr("Choose Low DPI Icon"), QDir::homePath(), tr("PNG images (*.png)")); + QString file = QFileDialog::getOpenFileName(this, tr("Choose Low DPI Icon"), QDir::homePath(), fileDialogIconFiles); if (file.isEmpty()) return; m_lIconPath = file; - m_lIconButton->setIcon(QIcon(file)); - setDirty(true); + QString baseDir = m_textEditorWidget->textDocument()->filePath().toFileInfo().absolutePath(); + copyIcon(LowDPI, baseDir, m_lIconPath); + m_lIconButton->setIcon(icon(baseDir, LowDPI)); } void AndroidManifestEditorWidget::setMDPIIcon() { - QString file = QFileDialog::getOpenFileName(this, tr("Choose Medium DPI Icon"), QDir::homePath(), tr("PNG images (*.png)")); + QString file = QFileDialog::getOpenFileName(this, tr("Choose Medium DPI Icon"), QDir::homePath(), fileDialogIconFiles); if (file.isEmpty()) return; m_mIconPath = file; - m_mIconButton->setIcon(QIcon(file)); - setDirty(true); + QString baseDir = m_textEditorWidget->textDocument()->filePath().toFileInfo().absolutePath(); + copyIcon(MediumDPI, baseDir, m_mIconPath); + m_mIconButton->setIcon(icon(baseDir, MediumDPI)); } void AndroidManifestEditorWidget::setHDPIIcon() { - QString file = QFileDialog::getOpenFileName(this, tr("Choose High DPI Icon"), QDir::homePath(), tr("PNG images (*.png)")); + QString file = QFileDialog::getOpenFileName(this, tr("Choose High DPI Icon"), QDir::homePath(), fileDialogIconFiles); if (file.isEmpty()) return; m_hIconPath = file; - m_hIconButton->setIcon(QIcon(file)); - setDirty(true); + QString baseDir = m_textEditorWidget->textDocument()->filePath().toFileInfo().absolutePath(); + copyIcon(HighDPI, baseDir, m_hIconPath); + m_hIconButton->setIcon(icon(baseDir, HighDPI)); } void AndroidManifestEditorWidget::clearLDPIIcon() { m_lIconPath.clear(); m_lIconButton->setIcon(QIcon()); + QString baseDir = m_textEditorWidget->textDocument()->filePath().toFileInfo().absolutePath(); + removeIcon(LowDPI, baseDir); } void AndroidManifestEditorWidget::clearMDPIIcon() { m_mIconPath.clear(); m_mIconButton->setIcon(QIcon()); + QString baseDir = m_textEditorWidget->textDocument()->filePath().toFileInfo().absolutePath(); + removeIcon(MediumDPI, baseDir); } void AndroidManifestEditorWidget::clearHDPIIcon() { m_hIconPath.clear(); m_hIconButton->setIcon(QIcon()); + QString baseDir = m_textEditorWidget->textDocument()->filePath().toFileInfo().absolutePath(); + removeIcon(HighDPI, baseDir); } void AndroidManifestEditorWidget::createDPIButton(QHBoxLayout *layout, QWidget *parent, QToolButton *&button, - QToolButton *&clearButton, + const QSize &buttonSize, const QString &title, - const QString &tooltip) + const QString &tooltip, + QToolButton **clearButton, + QLabel **scaleWarningLabel) { auto iconLayout = new QVBoxLayout(); auto iconTitle = new QLabel(title, parent); auto iconButtonLayout = new QGridLayout(); button = new QToolButton(parent); - button->setMinimumSize(QSize(48, 48)); - button->setMaximumSize(QSize(48, 48)); + button->setMinimumSize(buttonSize); + button->setMaximumSize(buttonSize); button->setToolTip(tooltip); - clearButton = new QToolButton(parent); - clearButton->setMinimumSize(QSize(16, 16)); - clearButton->setMaximumSize(QSize(16, 16)); - clearButton->setIcon(Utils::Icons::CLOSE_FOREGROUND.icon()); + button->setIconSize(buttonSize); + QSize clearAndWarningSize(16, 16); + if (clearButton) { + *clearButton = new QToolButton(parent); + (*clearButton)->setMinimumSize(clearAndWarningSize); + (*clearButton)->setMaximumSize(clearAndWarningSize); + (*clearButton)->setIcon(Utils::Icons::CLOSE_FOREGROUND.icon()); + } + if (scaleWarningLabel) { + *scaleWarningLabel = new QLabel(parent); + (*scaleWarningLabel)->setMinimumSize(clearAndWarningSize); + (*scaleWarningLabel)->setMaximumSize(clearAndWarningSize); + (*scaleWarningLabel)->setPixmap(Utils::Icons::WARNING.icon().pixmap(clearAndWarningSize)); + (*scaleWarningLabel)->setToolTip(tr("Icon scaled up")); + (*scaleWarningLabel)->setVisible(false); + } auto label = new QLabel(tr("Click to select"), parent); iconLayout->addWidget(iconTitle); iconLayout->setAlignment(iconTitle, Qt::AlignHCenter); iconButtonLayout->setColumnMinimumWidth(0, 16); iconButtonLayout->addWidget(button, 0, 1, 1, 3); iconButtonLayout->setAlignment(button, Qt::AlignVCenter); - iconButtonLayout->addWidget(clearButton, 0, 4, 1, 1); - iconButtonLayout->setAlignment(clearButton, Qt::AlignTop); + if (clearButton) { + iconButtonLayout->addWidget(*clearButton, 0, 4, 1, 1); + iconButtonLayout->setAlignment(*clearButton, Qt::AlignTop); + } + if (scaleWarningLabel) { + iconButtonLayout->addWidget(*scaleWarningLabel, 0, 0, 1, 1); + iconButtonLayout->setAlignment(*scaleWarningLabel, Qt::AlignTop); + } iconLayout->addLayout(iconButtonLayout); iconLayout->setAlignment(iconButtonLayout, Qt::AlignHCenter); iconLayout->addWidget(label); diff --git a/src/plugins/android/androidmanifesteditorwidget.h b/src/plugins/android/androidmanifesteditorwidget.h index 48349cd8684..efbc2b529a4 100644 --- a/src/plugins/android/androidmanifesteditorwidget.h +++ b/src/plugins/android/androidmanifesteditorwidget.h @@ -116,6 +116,8 @@ protected: void focusInEvent(QFocusEvent *event) override; private: + void setMasterIcon(); + void clearMasterIcon(); void setLDPIIcon(); void setMDPIIcon(); void setHDPIIcon(); @@ -124,8 +126,11 @@ private: void clearHDPIIcon(); void createDPIButton(QHBoxLayout *layout, QWidget *parent, - QToolButton *&button, QToolButton *&clearButton, - const QString &title, const QString &tooltip); + QToolButton *&button, const QSize &buttonSize, + const QString &title, const QString &tooltip, + QToolButton **clearButton = nullptr, + QLabel **scaleWarningLabel = nullptr + ); void defaultPermissionOrFeatureCheckBoxClicked(); void addPermission(); void removePermission(); @@ -146,7 +151,11 @@ private: enum IconDPI { LowDPI, MediumDPI, HighDPI }; QIcon icon(const QString &baseDir, IconDPI dpi); QString iconPath(IconDPI dpi); + QSize iconSize(IconDPI dpi); + void updateIconPath(const QString &newPath, IconDPI dpi); void copyIcon(IconDPI dpi, const QString &baseDir, const QString &filePath); + void removeIcon(IconDPI dpi, const QString &baseDir); + void toggleIconScaleWarning(IconDPI dpi, bool visible); void updateInfoBar(const QString &errorMessage, int line, int column); void hideInfoBar(); @@ -180,13 +189,17 @@ private: QLineEdit *m_appNameLineEdit; QLineEdit *m_activityNameLineEdit; QComboBox *m_targetLineEdit; + QToolButton *m_masterIconButton; QToolButton *m_lIconButton; QToolButton *m_lIconClearButton; + QLabel *m_lIconScaleWarningLabel; QToolButton *m_mIconButton; QToolButton *m_mIconClearButton; + QLabel *m_mIconScaleWarningLabel; QToolButton *m_hIconButton; QToolButton *m_hIconClearButton; - QString m_lIconPath; // only set if the user changed the icon + QLabel *m_hIconScaleWarningLabel; + QString m_lIconPath; QString m_mIconPath; QString m_hIconPath;