File indexing completed on 2024-04-21 05:31:18

0001 /*
0002     SPDX-FileCopyrightText: 2012 Marco Martin <mart@kde.org>
0003     SPDX-FileCopyrightText: 2014 David Edmundson <davidedmudnson@kde.org>
0004     SPDX-FileCopyrightText: 2016 Smith AR <audoban@openmailbox.org>
0005     SPDX-FileCopyrightText: 2016 Michail Vourlakos <mvourlakos@gmail.com>
0006 
0007     This file is part of Latte-Dock and is a Fork of PlasmaCore::IconItem
0008 
0009     SPDX-License-Identifier: GPL-2.0-or-later
0010 */
0011 
0012 #include "iconitem.h"
0013 
0014 // local
0015 #include "extras.h"
0016 
0017 // Qt
0018 #include <QDebug>
0019 #include <QPainter>
0020 #include <QPaintEngine>
0021 #include <QQuickWindow>
0022 #include <QPixmap>
0023 #include <QSGSimpleTextureNode>
0024 #include <QuickAddons/ManagedTextureNode>
0025 #include <QLatin1String>
0026 
0027 // KDE
0028 #include <KIconTheme>
0029 #include <KIconThemes/KIconLoader>
0030 #include <KIconThemes/KIconEffect>
0031 
0032 namespace Latte {
0033 
0034 IconItem::IconItem(QQuickItem *parent)
0035     : QQuickItem(parent),
0036       m_active(false),
0037       m_smooth(false),
0038       m_textureChanged(false),
0039       m_sizeChanged(false),
0040       m_usesPlasmaTheme(false),
0041       m_lastValidSourceName(QString()),
0042       m_colorGroup(Plasma::Theme::NormalColorGroup)
0043 {
0044     setFlag(ItemHasContents, true);
0045     connect(KIconLoader::global(), SIGNAL(iconLoaderSettingsChanged()),
0046             this, SIGNAL(implicitWidthChanged()));
0047     connect(KIconLoader::global(), SIGNAL(iconLoaderSettingsChanged()),
0048             this, SIGNAL(implicitHeightChanged()));
0049     connect(this, &QQuickItem::enabledChanged,
0050             this, &IconItem::enabledChanged);
0051     connect(this, &QQuickItem::windowChanged,
0052             this, &IconItem::schedulePixmapUpdate);
0053     connect(this, SIGNAL(overlaysChanged()),
0054             this, SLOT(schedulePixmapUpdate()));
0055     connect(this, SIGNAL(providesColorsChanged()),
0056             this, SLOT(schedulePixmapUpdate()));
0057 
0058     //initialize implicit size to the Dialog size
0059     setImplicitWidth(KIconLoader::global()->currentSize(KIconLoader::Dialog));
0060     setImplicitHeight(KIconLoader::global()->currentSize(KIconLoader::Dialog));
0061     setSmooth(true);
0062 }
0063 
0064 IconItem::~IconItem()
0065 {
0066 }
0067 
0068 void IconItem::setSource(const QVariant &source)
0069 {
0070     if (source == m_source) {
0071         return;
0072     }
0073 
0074     m_source = source;
0075     QString sourceString = source.toString();
0076 
0077     // If the QIcon was created with QIcon::fromTheme(), try to load it as svg
0078     if (source.canConvert<QIcon>() && !source.value<QIcon>().name().isEmpty()) {
0079         sourceString = source.value<QIcon>().name();
0080     }
0081 
0082     if (!sourceString.isEmpty()) {
0083         setLastValidSourceName(sourceString);
0084         setLastLoadedSourceId(sourceString);
0085 
0086         //If a url in the form file:// is passed, take the image pointed by that from disk
0087         QUrl url(sourceString);
0088 
0089         if (url.isLocalFile()) {
0090             m_icon = QIcon();
0091             m_imageIcon = QImage(url.path());
0092             m_svgIconName.clear();
0093             m_svgIcon.reset();
0094         } else {
0095             if (!m_svgIcon) {
0096                 m_svgIcon = std::make_unique<Plasma::Svg>(this);
0097                 m_svgIcon->setColorGroup(m_colorGroup);
0098                 m_svgIcon->setStatus(Plasma::Svg::Normal);
0099                 m_svgIcon->setUsingRenderingCache(false);
0100                 m_svgIcon->setDevicePixelRatio((window() ? window()->devicePixelRatio() : qApp->devicePixelRatio()));
0101                 connect(m_svgIcon.get(), &Plasma::Svg::repaintNeeded, this, &IconItem::schedulePixmapUpdate);
0102             }
0103 
0104             if (m_usesPlasmaTheme) {
0105                 //try as a svg icon from plasma theme
0106                 m_svgIcon->setImagePath(QLatin1String("icons/") + sourceString.split('-').first());
0107                 m_svgIcon->setContainsMultipleImages(true);
0108                 //invalidate the image path to recalculate it later
0109             } else {
0110                 m_svgIcon->setImagePath(QString());
0111             }
0112 
0113             //success?
0114             if (m_svgIcon->isValid() && m_svgIcon->hasElement(sourceString)) {
0115                 m_icon = QIcon();
0116                 m_svgIconName = sourceString;
0117                 //ok, svg not available from the plasma theme
0118             } else {
0119                 //try to load from iconloader an svg with Plasma::Svg
0120                 const auto *iconTheme = KIconLoader::global()->theme();
0121                 QString iconPath;
0122 
0123                 if (iconTheme) {
0124                     iconPath = iconTheme->iconPath(sourceString + QLatin1String(".svg")
0125                                                    , static_cast<int>(qMin(width(), height()))
0126                                                    , KIconLoader::MatchBest);
0127 
0128                     if (iconPath.isEmpty()) {
0129                         iconPath = iconTheme->iconPath(sourceString + QLatin1String(".svgz")
0130                                                        , static_cast<int>(qMin(width(), height()))
0131                                                        , KIconLoader::MatchBest);
0132                     }
0133                 } else {
0134                     qWarning() << "KIconLoader has no theme set";
0135                 }
0136 
0137                 if (!iconPath.isEmpty()) {
0138                     m_svgIcon->setImagePath(iconPath);
0139                     m_svgIconName = sourceString;
0140                     //fail, use QIcon
0141                 } else {
0142                     //if we started with a QIcon use that.
0143                     m_icon = source.value<QIcon>();
0144 
0145                     if (m_icon.isNull()) {
0146                         m_icon = QIcon::fromTheme(sourceString);
0147                     }
0148 
0149                     m_svgIconName.clear();
0150                     m_svgIcon.reset();
0151                     m_imageIcon = QImage();
0152                 }
0153             }
0154         }
0155     } else if (source.canConvert<QIcon>()) {
0156         m_icon = source.value<QIcon>();
0157         m_iconCounter++;
0158         setLastLoadedSourceId("_icon_"+QString::number(m_iconCounter));
0159 
0160         m_imageIcon = QImage();
0161         m_svgIconName.clear();
0162         m_svgIcon.reset();
0163     } else if (source.canConvert<QImage>()) {
0164         m_imageIcon = source.value<QImage>();
0165         m_iconCounter++;
0166         setLastLoadedSourceId("_image_"+QString::number(m_iconCounter));
0167 
0168         m_icon = QIcon();
0169         m_svgIconName.clear();
0170         m_svgIcon.reset();
0171     } else {
0172         m_icon = QIcon();
0173         m_imageIcon = QImage();
0174         m_svgIconName.clear();
0175         m_svgIcon.reset();
0176     }
0177 
0178     if (width() > 0 && height() > 0) {
0179         schedulePixmapUpdate();
0180     }
0181 
0182     emit sourceChanged();
0183     emit validChanged();
0184 }
0185 
0186 QVariant IconItem::source() const
0187 {
0188     return m_source;
0189 }
0190 
0191 void IconItem::setLastLoadedSourceId(QString id)
0192 {
0193     if (m_lastLoadedSourceId == id) {
0194         return;
0195     }
0196 
0197     m_lastLoadedSourceId = id;
0198 }
0199 
0200 QString IconItem::lastValidSourceName()
0201 {
0202     return m_lastValidSourceName;
0203 }
0204 
0205 void IconItem::setLastValidSourceName(QString name)
0206 {
0207     if (m_lastValidSourceName == name || name.isEmpty() || name == QLatin1String("application-x-executable")) {
0208         return;
0209     }
0210 
0211     m_lastValidSourceName = name;
0212 
0213     emit lastValidSourceNameChanged();
0214 }
0215 
0216 void IconItem::setColorGroup(Plasma::Theme::ColorGroup group)
0217 {
0218     if (m_colorGroup == group) {
0219         return;
0220     }
0221 
0222     m_colorGroup = group;
0223 
0224     if (m_svgIcon) {
0225         m_svgIcon->setColorGroup(group);
0226     }
0227 
0228     emit colorGroupChanged();
0229 }
0230 
0231 Plasma::Theme::ColorGroup IconItem::colorGroup() const
0232 {
0233     return m_colorGroup;
0234 }
0235 
0236 
0237 void IconItem::setOverlays(const QStringList &overlays)
0238 {
0239     if (overlays == m_overlays) {
0240         return;
0241     }
0242 
0243     m_overlays = overlays;
0244     emit overlaysChanged();
0245 }
0246 
0247 QStringList IconItem::overlays() const
0248 {
0249     return m_overlays;
0250 }
0251 
0252 
0253 bool IconItem::isActive() const
0254 {
0255     return m_active;
0256 }
0257 
0258 void IconItem::setActive(bool active)
0259 {
0260     if (m_active == active) {
0261         return;
0262     }
0263 
0264     m_active = active;
0265 
0266     if (isComponentComplete()) {
0267         schedulePixmapUpdate();
0268     }
0269 
0270     emit activeChanged();
0271 }
0272 
0273 bool IconItem::providesColors() const
0274 {
0275     return m_providesColors;
0276 }
0277 
0278 void IconItem::setProvidesColors(const bool provides)
0279 {
0280     if (m_providesColors == provides) {
0281         return;
0282     }
0283 
0284     m_providesColors = provides;
0285     emit providesColorsChanged();
0286 }
0287 
0288 void IconItem::setSmooth(const bool smooth)
0289 {
0290     if (smooth == m_smooth) {
0291         return;
0292     }
0293 
0294     m_smooth = smooth;
0295     update();
0296 }
0297 
0298 bool IconItem::smooth() const
0299 {
0300     return m_smooth;
0301 }
0302 
0303 bool IconItem::isValid() const
0304 {
0305     return !m_icon.isNull() || m_svgIcon || !m_imageIcon.isNull();
0306 }
0307 
0308 int IconItem::paintedWidth() const
0309 {
0310     return boundingRect().size().toSize().width();
0311 }
0312 
0313 int IconItem::paintedHeight() const
0314 {
0315     return boundingRect().size().toSize().height();
0316 }
0317 
0318 bool IconItem::usesPlasmaTheme() const
0319 {
0320     return m_usesPlasmaTheme;
0321 }
0322 
0323 void IconItem::setUsesPlasmaTheme(bool usesPlasmaTheme)
0324 {
0325     if (m_usesPlasmaTheme == usesPlasmaTheme) {
0326         return;
0327     }
0328 
0329     m_usesPlasmaTheme = usesPlasmaTheme;
0330 
0331     // Reload icon with new settings
0332     const QVariant src = m_source;
0333     m_source.clear();
0334     setSource(src);
0335 
0336     update();
0337     emit usesPlasmaThemeChanged();
0338 }
0339 
0340 void IconItem::updatePolish()
0341 {
0342     QQuickItem::updatePolish();
0343     loadPixmap();
0344 }
0345 
0346 QSGNode *IconItem::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *updatePaintNodeData)
0347 {
0348     Q_UNUSED(updatePaintNodeData)
0349 
0350     if (m_iconPixmap.isNull() || width() < 1.0 || height() < 1.0) {
0351         delete oldNode;
0352         return nullptr;
0353     }
0354 
0355     ManagedTextureNode *textureNode = dynamic_cast<ManagedTextureNode *>(oldNode);
0356 
0357     if (!textureNode || m_textureChanged) {
0358         if (oldNode)
0359             delete oldNode;
0360 
0361         textureNode = new ManagedTextureNode;
0362         textureNode->setTexture(QSharedPointer<QSGTexture>(window()->createTextureFromImage(m_iconPixmap.toImage(), QQuickWindow::TextureCanUseAtlas)));
0363         textureNode->setFiltering(smooth() ? QSGTexture::Linear : QSGTexture::Nearest);
0364 
0365         m_sizeChanged = true;
0366         m_textureChanged = false;
0367     }
0368 
0369     if (m_sizeChanged) {
0370         const auto iconSize = qMin(boundingRect().size().width(), boundingRect().size().height());
0371         const QRectF destRect(QPointF(boundingRect().center() - QPointF(iconSize / 2, iconSize / 2)), QSizeF(iconSize, iconSize));
0372         textureNode->setRect(destRect);
0373         m_sizeChanged = false;
0374     }
0375 
0376     return textureNode;
0377 }
0378 
0379 void IconItem::schedulePixmapUpdate()
0380 {
0381     polish();
0382 }
0383 
0384 void IconItem::enabledChanged()
0385 {
0386     schedulePixmapUpdate();
0387 }
0388 
0389 QColor IconItem::backgroundColor() const
0390 {
0391     return m_backgroundColor;
0392 }
0393 
0394 void IconItem::setBackgroundColor(QColor background)
0395 {
0396     if (m_backgroundColor == background) {
0397         return;
0398     }
0399 
0400     m_backgroundColor = background;
0401     emit backgroundColorChanged();
0402 }
0403 
0404 QColor IconItem::glowColor() const
0405 {
0406     return m_glowColor;
0407 }
0408 
0409 void IconItem::setGlowColor(QColor glow)
0410 {
0411     if (m_glowColor == glow) {
0412         return;
0413     }
0414 
0415     m_glowColor = glow;
0416     emit glowColorChanged();
0417 }
0418 
0419 void IconItem::updateColors()
0420 {
0421     QImage icon = m_iconPixmap.toImage();
0422 
0423     if (icon.format() != QImage::Format_Invalid) {
0424         float rtotal = 0, gtotal = 0, btotal = 0;
0425         float total = 0.0f;
0426 
0427         for(int row=0; row<icon.height(); ++row) {
0428             QRgb *line = (QRgb *)icon.scanLine(row);
0429 
0430             for(int col=0; col<icon.width(); ++col) {
0431                 QRgb pix = line[col];
0432 
0433                 int r = qRed(pix);
0434                 int g = qGreen(pix);
0435                 int b = qBlue(pix);
0436                 int a = qAlpha(pix);
0437 
0438                 float saturation = (qMax(r, qMax(g, b)) - qMin(r, qMin(g, b))) / 255.0f;
0439                 float relevance = .1 + .9 * (a / 255.0f) * saturation;
0440 
0441                 rtotal += (float)(r * relevance);
0442                 gtotal += (float)(g * relevance);
0443                 btotal += (float)(b * relevance);
0444 
0445                 total += relevance * 255;
0446             }
0447         }
0448 
0449         int nr = (rtotal / total) * 255;
0450         int ng = (gtotal / total) * 255;
0451         int nb = (btotal / total) * 255;
0452 
0453         QColor tempColor(nr, ng, nb);
0454 
0455         if (tempColor.hsvSaturationF() > 0.15f) {
0456             tempColor.setHsvF(tempColor.hueF(), 0.65f, tempColor.valueF());
0457         }
0458 
0459         tempColor.setHsvF(tempColor.hueF(), tempColor.saturationF(), 0.55f); //original 0.90f ???
0460 
0461         setBackgroundColor(tempColor);
0462 
0463         tempColor.setHsvF(tempColor.hueF(), tempColor.saturationF(), 1.0f);
0464 
0465         setGlowColor(tempColor);
0466     }
0467 }
0468 
0469 void IconItem::loadPixmap()
0470 {
0471     if (!isComponentComplete()) {
0472         return;
0473     }
0474 
0475     const auto size = qMin(width(), height());
0476     //final pixmap to paint
0477     QPixmap result;
0478 
0479     if (size <= 0) {
0480         m_iconPixmap = QPixmap();
0481         update();
0482         return;
0483     } else if (m_svgIcon) {
0484         m_svgIcon->resize(size, size);
0485 
0486         if (m_svgIcon->hasElement(m_svgIconName)) {
0487             result = m_svgIcon->pixmap(m_svgIconName);
0488         } else if (!m_svgIconName.isEmpty()) {
0489             const auto *iconTheme = KIconLoader::global()->theme();
0490             QString iconPath;
0491 
0492             if (iconTheme) {
0493                 iconPath = iconTheme->iconPath(m_svgIconName + QLatin1String(".svg")
0494                                                , static_cast<int>(qMin(width(), height()))
0495                                                , KIconLoader::MatchBest);
0496 
0497                 if (iconPath.isEmpty()) {
0498                     iconPath = iconTheme->iconPath(m_svgIconName + QLatin1String(".svgz"),
0499                                                    static_cast<int>(qMin(width(), height()))
0500                                                    , KIconLoader::MatchBest);
0501                 }
0502             } else {
0503                 qWarning() << "KIconLoader has no theme set";
0504             }
0505 
0506             if (!iconPath.isEmpty()) {
0507                 m_svgIcon->setImagePath(iconPath);
0508             }
0509 
0510             result = m_svgIcon->pixmap();
0511         }
0512     } else if (!m_icon.isNull()) {
0513         result = m_icon.pixmap(QSize(static_cast<int>(size), static_cast<int>(size))
0514                                * (window() ? window()->devicePixelRatio() : qApp->devicePixelRatio()));
0515     } else if (!m_imageIcon.isNull()) {
0516         result = QPixmap::fromImage(m_imageIcon);
0517     } else {
0518         m_iconPixmap = QPixmap();
0519         update();
0520         return;
0521     }
0522 
0523     // Strangely KFileItem::overlays() returns empty string-values, so
0524     // we need to check first whether an overlay must be drawn at all.
0525     // It is more efficient to do it here, as KIconLoader::drawOverlays()
0526     // assumes that an overlay will be drawn and has some additional
0527     // setup time.
0528     for (const QString &overlay : m_overlays) {
0529         if (!overlay.isEmpty()) {
0530             // There is at least one overlay, draw all overlays above m_pixmap
0531             // and cancel the check
0532             KIconLoader::global()->drawOverlays(m_overlays, result, KIconLoader::Desktop);
0533             break;
0534         }
0535     }
0536 
0537     if (!isEnabled()) {
0538         result = KIconLoader::global()->iconEffect()->apply(result, KIconLoader::Desktop, KIconLoader::DisabledState);
0539     } else if (m_active) {
0540         result = KIconLoader::global()->iconEffect()->apply(result, KIconLoader::Desktop, KIconLoader::ActiveState);
0541     }
0542 
0543     m_iconPixmap = result;
0544 
0545     if (m_providesColors && m_lastLoadedSourceId != m_lastColorsSourceId) {
0546         m_lastColorsSourceId = m_lastLoadedSourceId;
0547         updateColors();
0548     }
0549 
0550     m_textureChanged = true;
0551     //don't animate initial setting
0552     update();
0553 }
0554 
0555 void IconItem::itemChange(ItemChange change, const ItemChangeData &value)
0556 {
0557     QQuickItem::itemChange(change, value);
0558 }
0559 
0560 void IconItem::geometryChanged(const QRectF &newGeometry, const QRectF &oldGeometry)
0561 {
0562     if (newGeometry.size() != oldGeometry.size()) {
0563         m_sizeChanged = true;
0564 
0565         if (newGeometry.width() > 1 && newGeometry.height() > 1) {
0566             schedulePixmapUpdate();
0567         } else {
0568             update();
0569         }
0570 
0571         const auto oldSize = qMin(oldGeometry.size().width(), oldGeometry.size().height());
0572         const auto newSize = qMin(newGeometry.size().width(), newGeometry.size().height());
0573 
0574         if (!almost_equal(oldSize, newSize, 2)) {
0575             emit paintedSizeChanged();
0576         }
0577     }
0578 
0579     QQuickItem::geometryChanged(newGeometry, oldGeometry);
0580 }
0581 
0582 void IconItem::componentComplete()
0583 {
0584     QQuickItem::componentComplete();
0585     schedulePixmapUpdate();
0586 }
0587 
0588 }