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 }