File indexing completed on 2024-04-14 03:53:46

0001 /*
0002  *  SPDX-FileCopyrightText: 2011 Marco Martin <mart@kde.org>
0003  *  SPDX-FileCopyrightText: 2014 Aleix Pol Gonzalez <aleixpol@blue-systems.com>
0004  *
0005  *  SPDX-License-Identifier: LGPL-2.0-or-later
0006  */
0007 
0008 #include "icon.h"
0009 #include "scenegraph/managedtexturenode.h"
0010 
0011 #include "platform/platformtheme.h"
0012 #include "platform/units.h"
0013 
0014 #include "loggingcategory.h"
0015 #include <QBitmap>
0016 #include <QDebug>
0017 #include <QGuiApplication>
0018 #include <QIcon>
0019 #include <QNetworkReply>
0020 #include <QPainter>
0021 #include <QPropertyAnimation>
0022 #include <QQuickImageProvider>
0023 #include <QQuickWindow>
0024 #include <QSGSimpleTextureNode>
0025 #include <QSGTexture>
0026 #include <QScreen>
0027 #include <cstdlib>
0028 
0029 Q_GLOBAL_STATIC(ImageTexturesCache, s_iconImageCache)
0030 
0031 Icon::Icon(QQuickItem *parent)
0032     : QQuickItem(parent)
0033     , m_active(false)
0034     , m_selected(false)
0035     , m_isMask(false)
0036 {
0037     setFlag(ItemHasContents, true);
0038     // Using 32 because Icon used to redefine implicitWidth and implicitHeight and hardcode them to 32
0039     setImplicitSize(32, 32);
0040 
0041     connect(this, &QQuickItem::smoothChanged, this, &QQuickItem::polish);
0042     connect(this, &QQuickItem::enabledChanged, this, [this]() {
0043         m_allowNextAnimation = true;
0044         polish();
0045     });
0046 }
0047 
0048 Icon::~Icon()
0049 {
0050 }
0051 
0052 void Icon::componentComplete()
0053 {
0054     QQuickItem::componentComplete();
0055 
0056     QQmlEngine *engine = qmlEngine(this);
0057     Q_ASSERT(engine);
0058     m_units = engine->singletonInstance<Kirigami::Platform::Units *>("org.kde.kirigami.platform", "Units");
0059     Q_ASSERT(m_units);
0060     m_animation = new QPropertyAnimation(this);
0061     connect(m_animation, &QPropertyAnimation::valueChanged, this, &Icon::valueChanged);
0062     connect(m_animation, &QPropertyAnimation::finished, this, [this]() {
0063         m_oldIcon = QImage();
0064         m_textureChanged = true;
0065         update();
0066     });
0067     m_animation->setTargetObject(this);
0068     m_animation->setEasingCurve(QEasingCurve::InOutCubic);
0069     m_animation->setDuration(m_units->longDuration());
0070     connect(m_units, &Kirigami::Platform::Units::longDurationChanged, m_animation, [this]() {
0071         m_animation->setDuration(m_units->longDuration());
0072     });
0073     updatePaintedGeometry();
0074 }
0075 
0076 void Icon::setSource(const QVariant &icon)
0077 {
0078     if (m_source == icon) {
0079         return;
0080     }
0081     m_source = icon;
0082 
0083     if (!m_theme) {
0084         m_theme = static_cast<Kirigami::Platform::PlatformTheme *>(qmlAttachedPropertiesObject<Kirigami::Platform::PlatformTheme>(this, true));
0085         Q_ASSERT(m_theme);
0086 
0087         connect(m_theme, &Kirigami::Platform::PlatformTheme::colorsChanged, this, &QQuickItem::polish);
0088     }
0089 
0090     if (m_networkReply) {
0091         // if there was a network query going on, interrupt it
0092         m_networkReply->close();
0093     }
0094     m_loadedImage = QImage();
0095     setStatus(Loading);
0096 
0097     polish();
0098     Q_EMIT sourceChanged();
0099     Q_EMIT validChanged();
0100 }
0101 
0102 QVariant Icon::source() const
0103 {
0104     return m_source;
0105 }
0106 
0107 void Icon::setActive(const bool active)
0108 {
0109     if (active == m_active) {
0110         return;
0111     }
0112     m_active = active;
0113     if (isComponentComplete()) {
0114         m_allowNextAnimation = true;
0115     }
0116     polish();
0117     Q_EMIT activeChanged();
0118 }
0119 
0120 bool Icon::active() const
0121 {
0122     return m_active;
0123 }
0124 
0125 bool Icon::valid() const
0126 {
0127     // TODO: should this be return m_status == Ready?
0128     // Consider an empty URL invalid, even though isNull() will say false
0129     if (m_source.canConvert<QUrl>() && m_source.toUrl().isEmpty()) {
0130         return false;
0131     }
0132 
0133     return !m_source.isNull();
0134 }
0135 
0136 void Icon::setSelected(const bool selected)
0137 {
0138     if (selected == m_selected) {
0139         return;
0140     }
0141     m_selected = selected;
0142     polish();
0143     Q_EMIT selectedChanged();
0144 }
0145 
0146 bool Icon::selected() const
0147 {
0148     return m_selected;
0149 }
0150 
0151 void Icon::setIsMask(bool mask)
0152 {
0153     if (m_isMask == mask) {
0154         return;
0155     }
0156 
0157     m_isMask = mask;
0158     polish();
0159     Q_EMIT isMaskChanged();
0160 }
0161 
0162 bool Icon::isMask() const
0163 {
0164     return m_isMask;
0165 }
0166 
0167 void Icon::setColor(const QColor &color)
0168 {
0169     if (m_color == color) {
0170         return;
0171     }
0172 
0173     m_color = color;
0174     polish();
0175     Q_EMIT colorChanged();
0176 }
0177 
0178 QColor Icon::color() const
0179 {
0180     return m_color;
0181 }
0182 
0183 QSGNode *Icon::createSubtree(qreal initialOpacity)
0184 {
0185     auto opacityNode = new QSGOpacityNode{};
0186     opacityNode->setFlag(QSGNode::OwnedByParent, true);
0187     opacityNode->setOpacity(initialOpacity);
0188 
0189     auto *mNode = new ManagedTextureNode;
0190 
0191     mNode->setTexture(s_iconImageCache->loadTexture(window(), m_icon, QQuickWindow::TextureCanUseAtlas));
0192 
0193     opacityNode->appendChildNode(mNode);
0194 
0195     return opacityNode;
0196 }
0197 
0198 void Icon::updateSubtree(QSGNode *node, qreal opacity)
0199 {
0200     auto opacityNode = static_cast<QSGOpacityNode *>(node);
0201     opacityNode->setOpacity(opacity);
0202 
0203     auto textureNode = static_cast<ManagedTextureNode *>(opacityNode->firstChild());
0204     textureNode->setFiltering(smooth() ? QSGTexture::Linear : QSGTexture::Nearest);
0205 }
0206 
0207 QSGNode *Icon::updatePaintNode(QSGNode *node, QQuickItem::UpdatePaintNodeData * /*data*/)
0208 {
0209     if (m_source.isNull() || qFuzzyIsNull(width()) || qFuzzyIsNull(height())) {
0210         delete node;
0211         return Q_NULLPTR;
0212     }
0213 
0214     if (!node) {
0215         node = new QSGNode{};
0216     }
0217 
0218     if (m_animation && m_animation->state() == QAbstractAnimation::Running) {
0219         if (node->childCount() < 2) {
0220             node->appendChildNode(createSubtree(0.0));
0221             m_textureChanged = true;
0222         }
0223 
0224         // Rather than doing a perfect crossfade, first fade in the new texture
0225         // then fade out the old texture. This is done to avoid the underlying
0226         // color bleeding through when both textures are at ~0.5 opacity, which
0227         // causes flickering if the two textures are very similar.
0228         updateSubtree(node->firstChild(), 2.0 - m_animValue * 2.0);
0229         updateSubtree(node->lastChild(), m_animValue * 2.0);
0230     } else {
0231         if (node->childCount() == 0) {
0232             node->appendChildNode(createSubtree(1.0));
0233             m_textureChanged = true;
0234         }
0235 
0236         if (node->childCount() > 1) {
0237             auto toRemove = node->firstChild();
0238             node->removeChildNode(toRemove);
0239             delete toRemove;
0240         }
0241 
0242         updateSubtree(node->firstChild(), 1.0);
0243     }
0244 
0245     if (m_textureChanged) {
0246         auto mNode = static_cast<ManagedTextureNode *>(node->lastChild()->firstChild());
0247         mNode->setTexture(s_iconImageCache->loadTexture(window(), m_icon, QQuickWindow::TextureCanUseAtlas));
0248         m_textureChanged = false;
0249         m_sizeChanged = true;
0250     }
0251 
0252     if (m_sizeChanged) {
0253         const QSizeF iconPixSize(m_icon.width() / m_devicePixelRatio, m_icon.height() / m_devicePixelRatio);
0254         const QSizeF itemPixSize = QSizeF((size() * m_devicePixelRatio).toSize()) / m_devicePixelRatio;
0255         QRectF nodeRect(QPoint(0, 0), itemPixSize);
0256 
0257         if (itemPixSize.width() != 0 && itemPixSize.height() != 0) {
0258             if (iconPixSize != itemPixSize) {
0259                 // At this point, the image will already be scaled, but we need to output it in
0260                 // the correct aspect ratio, painted centered in the viewport. So:
0261                 QRectF destination(QPointF(0, 0), QSizeF(m_icon.size()).scaled(m_paintedSize, Qt::KeepAspectRatio));
0262                 destination.moveCenter(nodeRect.center());
0263                 destination.moveTopLeft(QPointF(destination.topLeft().toPoint() * m_devicePixelRatio) / m_devicePixelRatio);
0264                 nodeRect = destination;
0265             }
0266         }
0267 
0268         // Adjust the final node on the pixel grid
0269         QPointF globalPixelPos = mapToScene(nodeRect.topLeft()) * m_devicePixelRatio;
0270         QPointF posAdjust = QPointF(globalPixelPos.x() - std::round(globalPixelPos.x()), globalPixelPos.y() - std::round(globalPixelPos.y()));
0271         nodeRect.moveTopLeft(nodeRect.topLeft() - posAdjust);
0272 
0273         for (int i = 0; i < node->childCount(); ++i) {
0274             auto mNode = static_cast<ManagedTextureNode *>(node->childAtIndex(i)->firstChild());
0275             mNode->setRect(nodeRect);
0276         }
0277 
0278         m_sizeChanged = false;
0279     }
0280 
0281     return node;
0282 }
0283 
0284 void Icon::geometryChange(const QRectF &newGeometry, const QRectF &oldGeometry)
0285 {
0286     QQuickItem::geometryChange(newGeometry, oldGeometry);
0287     if (newGeometry.size() != oldGeometry.size()) {
0288         m_sizeChanged = true;
0289         updatePaintedGeometry();
0290         polish();
0291     }
0292 }
0293 
0294 void Icon::handleRedirect(QNetworkReply *reply)
0295 {
0296     QNetworkAccessManager *qnam = reply->manager();
0297     if (reply->error() != QNetworkReply::NoError) {
0298         return;
0299     }
0300     const QUrl possibleRedirectUrl = reply->attribute(QNetworkRequest::RedirectionTargetAttribute).toUrl();
0301     if (!possibleRedirectUrl.isEmpty()) {
0302         const QUrl redirectUrl = reply->url().resolved(possibleRedirectUrl);
0303         if (redirectUrl == reply->url()) {
0304             // no infinite redirections thank you very much
0305             reply->deleteLater();
0306             return;
0307         }
0308         reply->deleteLater();
0309         QNetworkRequest request(possibleRedirectUrl);
0310         request.setAttribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::PreferCache);
0311         m_networkReply = qnam->get(request);
0312         connect(m_networkReply.data(), &QNetworkReply::finished, this, [this]() {
0313             handleFinished(m_networkReply);
0314         });
0315     }
0316 }
0317 
0318 void Icon::handleFinished(QNetworkReply *reply)
0319 {
0320     if (!reply) {
0321         return;
0322     }
0323 
0324     reply->deleteLater();
0325     if (!reply->attribute(QNetworkRequest::RedirectionTargetAttribute).isNull()) {
0326         handleRedirect(reply);
0327         return;
0328     }
0329 
0330     m_loadedImage = QImage();
0331 
0332     const QString filename = reply->url().fileName();
0333     if (!m_loadedImage.load(reply, filename.mid(filename.indexOf(QLatin1Char('.'))).toLatin1().constData())) {
0334         qCWarning(KirigamiLog) << "received broken image" << reply->url();
0335 
0336         // broken image from data, inform the user of this with some useful broken-image thing...
0337         m_loadedImage = iconPixmap(QIcon::fromTheme(m_fallback));
0338     }
0339 
0340     polish();
0341 }
0342 
0343 void Icon::updatePolish()
0344 {
0345     QQuickItem::updatePolish();
0346 
0347     if (window()) {
0348         m_devicePixelRatio = window()->effectiveDevicePixelRatio();
0349     }
0350 
0351     if (m_source.isNull()) {
0352         setStatus(Ready);
0353         updatePaintedGeometry();
0354         update();
0355         return;
0356     }
0357 
0358     const QSize itemSize(width(), height());
0359     if (itemSize.width() != 0 && itemSize.height() != 0) {
0360         const QSize size = itemSize;
0361 
0362         if (m_animation) {
0363             m_animation->stop();
0364             m_oldIcon = m_icon;
0365         }
0366 
0367         switch (m_source.userType()) {
0368         case QMetaType::QPixmap:
0369             m_icon = m_source.value<QPixmap>().toImage();
0370             break;
0371         case QMetaType::QImage:
0372             m_icon = m_source.value<QImage>();
0373             break;
0374         case QMetaType::QBitmap:
0375             m_icon = m_source.value<QBitmap>().toImage();
0376             break;
0377         case QMetaType::QIcon: {
0378             m_icon = iconPixmap(m_source.value<QIcon>());
0379             break;
0380         }
0381         case QMetaType::QUrl:
0382         case QMetaType::QString:
0383             m_icon = findIcon(size);
0384             break;
0385         case QMetaType::QBrush:
0386             // todo: fill here too?
0387         case QMetaType::QColor:
0388             m_icon = QImage(size, QImage::Format_Alpha8);
0389             m_icon.fill(m_source.value<QColor>());
0390             break;
0391         default:
0392             break;
0393         }
0394 
0395         if (m_icon.isNull()) {
0396             m_icon = QImage(size, QImage::Format_Alpha8);
0397             m_icon.fill(Qt::transparent);
0398         }
0399 
0400         const QColor tintColor = //
0401             !m_color.isValid() || m_color == Qt::transparent //
0402             ? (m_selected ? m_theme->highlightedTextColor() : m_theme->textColor())
0403             : m_color;
0404 
0405         // TODO: initialize m_isMask with icon.isMask()
0406         if (tintColor.alpha() > 0 && isMask()) {
0407             QPainter p(&m_icon);
0408             p.setCompositionMode(QPainter::CompositionMode_SourceIn);
0409             p.fillRect(m_icon.rect(), tintColor);
0410             p.end();
0411         }
0412     }
0413 
0414     // don't animate initial setting
0415     bool animated = (m_animated || m_allowNextAnimation) && !m_oldIcon.isNull() && !m_sizeChanged && !m_blockNextAnimation;
0416 
0417     if (animated && m_animation) {
0418         m_animValue = 0.0;
0419         m_animation->setStartValue((qreal)0);
0420         m_animation->setEndValue((qreal)1);
0421         m_animation->start();
0422         m_allowNextAnimation = false;
0423     } else {
0424         if (m_animation) {
0425             m_animation->stop();
0426         }
0427         m_animValue = 1.0;
0428         m_blockNextAnimation = false;
0429     }
0430     m_textureChanged = true;
0431     updatePaintedGeometry();
0432     update();
0433 }
0434 
0435 QImage Icon::findIcon(const QSize &size)
0436 {
0437     QImage img;
0438     QString iconSource = m_source.toString();
0439 
0440     if (iconSource.startsWith(QLatin1String("image://"))) {
0441         QUrl iconUrl(iconSource);
0442         QString iconProviderId = iconUrl.host();
0443         // QUrl path has the  "/" prefix while iconId does not
0444         QString iconId = iconUrl.path().remove(0, 1);
0445 
0446         QSize actualSize;
0447         QQuickImageProvider *imageProvider = dynamic_cast<QQuickImageProvider *>(qmlEngine(this)->imageProvider(iconProviderId));
0448         if (!imageProvider) {
0449             return img;
0450         }
0451         switch (imageProvider->imageType()) {
0452         case QQmlImageProviderBase::Image:
0453             img = imageProvider->requestImage(iconId, &actualSize, size);
0454             if (!img.isNull()) {
0455                 setStatus(Ready);
0456             }
0457             break;
0458         case QQmlImageProviderBase::Pixmap:
0459             img = imageProvider->requestPixmap(iconId, &actualSize, size).toImage();
0460             if (!img.isNull()) {
0461                 setStatus(Ready);
0462             }
0463             break;
0464         case QQmlImageProviderBase::ImageResponse: {
0465             if (!m_loadedImage.isNull()) {
0466                 setStatus(Ready);
0467                 return m_loadedImage.scaled(size, Qt::KeepAspectRatio, smooth() ? Qt::SmoothTransformation : Qt::FastTransformation);
0468             }
0469             QQuickAsyncImageProvider *provider = dynamic_cast<QQuickAsyncImageProvider *>(imageProvider);
0470             auto response = provider->requestImageResponse(iconId, size);
0471             connect(response, &QQuickImageResponse::finished, this, [iconId, response, this]() {
0472                 if (response->errorString().isEmpty()) {
0473                     QQuickTextureFactory *textureFactory = response->textureFactory();
0474                     if (textureFactory) {
0475                         m_loadedImage = textureFactory->image();
0476                         delete textureFactory;
0477                     }
0478                     if (m_loadedImage.isNull()) {
0479                         // broken image from data, inform the user of this with some useful broken-image thing...
0480                         m_loadedImage = iconPixmap(QIcon::fromTheme(m_fallback));
0481                         setStatus(Error);
0482                     } else {
0483                         setStatus(Ready);
0484                     }
0485                     polish();
0486                 }
0487                 response->deleteLater();
0488             });
0489             // Temporary icon while we wait for the real image to load...
0490             img = iconPixmap(QIcon::fromTheme(m_placeholder));
0491             break;
0492         }
0493         case QQmlImageProviderBase::Texture: {
0494             QQuickTextureFactory *textureFactory = imageProvider->requestTexture(iconId, &actualSize, size);
0495             if (textureFactory) {
0496                 img = textureFactory->image();
0497             }
0498             if (img.isNull()) {
0499                 // broken image from data, or the texture factory wasn't healthy, inform the user of this with some useful broken-image thing...
0500                 img = iconPixmap(QIcon::fromTheme(m_fallback));
0501                 setStatus(Error);
0502             } else {
0503                 setStatus(Ready);
0504             }
0505             break;
0506         }
0507         case QQmlImageProviderBase::Invalid:
0508             // will have to investigate this more
0509             setStatus(Error);
0510             break;
0511         }
0512     } else if (iconSource.startsWith(QLatin1String("http://")) || iconSource.startsWith(QLatin1String("https://"))) {
0513         if (!m_loadedImage.isNull()) {
0514             setStatus(Ready);
0515             return m_loadedImage.scaled(size, Qt::KeepAspectRatio, smooth() ? Qt::SmoothTransformation : Qt::FastTransformation);
0516         }
0517         const auto url = m_source.toUrl();
0518         QQmlEngine *engine = qmlEngine(this);
0519         QNetworkAccessManager *qnam;
0520         if (engine && (qnam = engine->networkAccessManager()) && (!m_networkReply || m_networkReply->url() != url)) {
0521             QNetworkRequest request(url);
0522             request.setAttribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::PreferCache);
0523             m_networkReply = qnam->get(request);
0524             connect(m_networkReply.data(), &QNetworkReply::finished, this, [this]() {
0525                 handleFinished(m_networkReply);
0526             });
0527         }
0528         // Temporary icon while we wait for the real image to load...
0529         img = iconPixmap(QIcon::fromTheme(m_placeholder));
0530     } else {
0531         if (iconSource.startsWith(QLatin1String("qrc:/"))) {
0532             iconSource = iconSource.mid(3);
0533         } else if (iconSource.startsWith(QLatin1String("file:/"))) {
0534             iconSource = QUrl(iconSource).path();
0535         }
0536 
0537         QIcon icon;
0538         const bool isPath = iconSource.contains(QLatin1String("/"));
0539         if (isPath) {
0540             icon = QIcon(iconSource);
0541         } else {
0542             const QColor tintColor =
0543                 !m_color.isValid() || m_color == Qt::transparent ? (m_selected ? m_theme->highlightedTextColor() : m_theme->textColor()) : m_color;
0544             icon = m_theme->iconFromTheme(iconSource, tintColor);
0545         }
0546         if (!icon.isNull()) {
0547             img = iconPixmap(icon);
0548             setStatus(Ready);
0549         }
0550     }
0551 
0552     if (!iconSource.isEmpty() && img.isNull()) {
0553         setStatus(Error);
0554         img = iconPixmap(QIcon::fromTheme(m_fallback));
0555     }
0556     return img;
0557 }
0558 
0559 QIcon::Mode Icon::iconMode() const
0560 {
0561     if (!isEnabled()) {
0562         return QIcon::Disabled;
0563     } else if (m_selected) {
0564         return QIcon::Selected;
0565     } else if (m_active) {
0566         return QIcon::Active;
0567     }
0568     return QIcon::Normal;
0569 }
0570 
0571 QString Icon::fallback() const
0572 {
0573     return m_fallback;
0574 }
0575 
0576 void Icon::setFallback(const QString &fallback)
0577 {
0578     if (m_fallback != fallback) {
0579         m_fallback = fallback;
0580         Q_EMIT fallbackChanged(fallback);
0581     }
0582 }
0583 
0584 QString Icon::placeholder() const
0585 {
0586     return m_placeholder;
0587 }
0588 
0589 void Icon::setPlaceholder(const QString &placeholder)
0590 {
0591     if (m_placeholder != placeholder) {
0592         m_placeholder = placeholder;
0593         Q_EMIT placeholderChanged(placeholder);
0594     }
0595 }
0596 
0597 void Icon::setStatus(Status status)
0598 {
0599     if (status == m_status) {
0600         return;
0601     }
0602 
0603     m_status = status;
0604     Q_EMIT statusChanged();
0605 }
0606 
0607 Icon::Status Icon::status() const
0608 {
0609     return m_status;
0610 }
0611 
0612 qreal Icon::paintedWidth() const
0613 {
0614     return std::round(m_paintedSize.width());
0615 }
0616 
0617 qreal Icon::paintedHeight() const
0618 {
0619     return std::round(m_paintedSize.height());
0620 }
0621 
0622 QSize Icon::iconSizeHint() const
0623 {
0624     if (!m_roundToIconSize) {
0625         return QSize(width(), height());
0626     } else if (m_units) {
0627         return QSize(m_units->iconSizes()->roundedIconSize(std::min(width(), height())), m_units->iconSizes()->roundedIconSize(std::min(width(), height())));
0628     } else {
0629         return QSize(std::min(width(), height()), std::min(width(), height()));
0630     }
0631 }
0632 
0633 QImage Icon::iconPixmap(const QIcon &icon) const
0634 {
0635     const QSize actualSize = icon.actualSize(iconSizeHint());
0636     return icon.pixmap(actualSize, m_devicePixelRatio, iconMode(), QIcon::On).toImage();
0637 }
0638 
0639 void Icon::updatePaintedGeometry()
0640 {
0641     QSizeF newSize;
0642     if (!m_icon.width() || !m_icon.height()) {
0643         newSize = {0, 0};
0644     } else {
0645         qreal roundedWidth = m_units ? m_units->iconSizes()->roundedIconSize(std::min(width(), height())) : 32;
0646         roundedWidth = std::round(roundedWidth * m_devicePixelRatio) / m_devicePixelRatio;
0647 
0648         if (QSizeF roundedSize(roundedWidth, roundedWidth); size() == roundedSize) {
0649             m_paintedSize = roundedSize;
0650             m_textureChanged = true;
0651             update();
0652             Q_EMIT paintedAreaChanged();
0653             return;
0654         }
0655         if (m_roundToIconSize && m_units) {
0656             if (m_icon.width() > m_icon.height()) {
0657                 newSize = QSizeF(roundedWidth, m_icon.height() * (roundedWidth / static_cast<qreal>(m_icon.width())));
0658             } else {
0659                 newSize = QSizeF(roundedWidth, roundedWidth);
0660             }
0661         } else {
0662             const QSizeF iconPixSize(m_icon.width() / m_devicePixelRatio, m_icon.height() / m_devicePixelRatio);
0663 
0664             const qreal w = widthValid() ? width() : iconPixSize.width();
0665             const qreal widthScale = w / iconPixSize.width();
0666             const qreal h = heightValid() ? height() : iconPixSize.height();
0667             const qreal heightScale = h / iconPixSize.height();
0668 
0669             if (widthScale <= heightScale) {
0670                 newSize = QSizeF(w, widthScale * iconPixSize.height());
0671             } else if (heightScale < widthScale) {
0672                 newSize = QSizeF(heightScale * iconPixSize.width(), h);
0673             }
0674         }
0675     }
0676     if (newSize != m_paintedSize) {
0677         m_paintedSize = newSize;
0678         m_textureChanged = true;
0679         update();
0680         Q_EMIT paintedAreaChanged();
0681     }
0682 }
0683 
0684 bool Icon::isAnimated() const
0685 {
0686     return m_animated;
0687 }
0688 
0689 void Icon::setAnimated(bool animated)
0690 {
0691     if (m_animated == animated) {
0692         return;
0693     }
0694 
0695     m_animated = animated;
0696     Q_EMIT animatedChanged();
0697 }
0698 
0699 bool Icon::roundToIconSize() const
0700 {
0701     return m_roundToIconSize;
0702 }
0703 
0704 void Icon::setRoundToIconSize(bool roundToIconSize)
0705 {
0706     if (m_roundToIconSize == roundToIconSize) {
0707         return;
0708     }
0709 
0710     const QSizeF oldPaintedSize = m_paintedSize;
0711 
0712     m_roundToIconSize = roundToIconSize;
0713     Q_EMIT roundToIconSizeChanged();
0714 
0715     updatePaintedGeometry();
0716     if (oldPaintedSize != m_paintedSize) {
0717         Q_EMIT paintedAreaChanged();
0718         m_textureChanged = true;
0719         update();
0720     }
0721 }
0722 
0723 void Icon::itemChange(QQuickItem::ItemChange change, const QQuickItem::ItemChangeData &value)
0724 {
0725     if (change == QQuickItem::ItemDevicePixelRatioHasChanged) {
0726         m_blockNextAnimation = true;
0727         if (window()) {
0728             m_devicePixelRatio = window()->effectiveDevicePixelRatio();
0729         }
0730         polish();
0731     } else if (change == QQuickItem::ItemSceneChange) {
0732         if (m_window) {
0733             disconnect(m_window.data(), &QWindow::visibleChanged, this, &Icon::windowVisibleChanged);
0734         }
0735         m_window = value.window;
0736         if (m_window) {
0737             connect(m_window.data(), &QWindow::visibleChanged, this, &Icon::windowVisibleChanged);
0738             m_devicePixelRatio = m_window->effectiveDevicePixelRatio();
0739         }
0740     } else if (change == ItemVisibleHasChanged && value.boolValue) {
0741         m_blockNextAnimation = true;
0742     }
0743     QQuickItem::itemChange(change, value);
0744 }
0745 
0746 void Icon::valueChanged(const QVariant &value)
0747 {
0748     m_animValue = value.toReal();
0749     update();
0750 }
0751 
0752 void Icon::windowVisibleChanged(bool visible)
0753 {
0754     if (visible) {
0755         m_blockNextAnimation = true;
0756     }
0757 }
0758 
0759 #include "moc_icon.cpp"