File indexing completed on 2024-04-28 16:49:38

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