diff --git a/autotests/iconitemtest.cpp b/autotests/iconitemtest.cpp index bc7221ce4..16fb9e812 100644 --- a/autotests/iconitemtest.cpp +++ b/autotests/iconitemtest.cpp @@ -1,518 +1,538 @@ /****************************************************************************** * Copyright 2016 David Rosca * * * * This library is free software; you can redistribute it and/or * * modify it under the terms of the GNU Library General Public * * License as published by the Free Software Foundation; either * * version 2 of the License, or (at your option) any later version. * * * * This library is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * Library General Public License for more details. * * * * You should have received a copy of the GNU Library General Public License * * along with this library; see the file COPYING.LIB. If not, write to * * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301, USA. * *******************************************************************************/ #include "iconitemtest.h" #include #include #include #include #include #include #include #include #include #include "plasma/theme.h" #include "plasma/svg.h" static bool imageIsEmpty(const QImage &img) { for (int i = 0; i < img.width(); ++i) { for (int j = 0; j < img.height(); ++j) { if (img.pixel(i, j) != 0) { return false; } } } return true; } void IconItemTest::initTestCase() { if (qgetenv("XDG_DATA_DIRS").isEmpty()) { QWARN("\n" " !!!\n" " Switching QStandardPaths into testing mode.\n" " Make sure xdg data can be found or set XDG_DATA_DIRS.\n" " !!!\n"); } // make our theme in search path qputenv("XDG_DATA_DIRS", qgetenv("XDG_DATA_DIRS") + ":" + QFINDTESTDATA("data").toLocal8Bit()); // set default icon theme to test-theme QStandardPaths::setTestModeEnabled(true); QString configPath = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation); if(!QDir(configPath).mkpath(QStringLiteral("."))) { qFatal("Failed to create test configuration directory."); } QFile::remove(configPath); QIcon::setThemeSearchPaths({QFINDTESTDATA("data/icons")}); QIcon::setThemeName("test-theme"); KIconTheme::forceThemeForTests("test-theme"); KIconTheme::reconfigure(); KIconLoader::global()->reconfigure(QString()); m_view = new QQuickView(); m_view->setSource(QUrl::fromLocalFile(QFINDTESTDATA("data/view.qml"))); m_view->show(); QTest::qWaitForWindowExposed(m_view); if (!m_view->rootObject() || !m_view->rootObject()->grabToImage()) { QSKIP("Cannot grab item to image."); } } void IconItemTest::cleanupTestCase() { delete m_view; } void IconItemTest::init() { Plasma::Theme().setThemeName(QStringLiteral("default")); } void IconItemTest::cleanup() { qDeleteAll(m_view->rootObject()->childItems()); } QQuickItem *IconItemTest::createIconItem() { QByteArray iconQml = "import QtQuick 2.0;" "import org.kde.plasma.core 2.0 as PlasmaCore;" "PlasmaCore.IconItem {" " id: root;" "}"; QQmlComponent component(m_view->engine()); QSignalSpy spy(&component, SIGNAL(statusChanged(QQmlComponent::Status))); component.setData(iconQml, QUrl("test://iconTest")); if (component.status() != QQmlComponent::Ready) { spy.wait(); } QQuickItem *item = qobject_cast(component.create(m_view->engine()->rootContext())); Q_ASSERT(item && qstrcmp(item->metaObject()->className(), "IconItem") == 0); item->setParentItem(m_view->rootObject()); return item; } QImage IconItemTest::grabImage(QQuickItem *item) { QSharedPointer grab = item->grabToImage(); QSignalSpy spy(grab.data(), SIGNAL(ready())); spy.wait(); return grab->image(); } Plasma::Svg *IconItemTest::findPlasmaSvg(QQuickItem *item) { return item->findChild(); } void IconItemTest::changeTheme(Plasma::Theme *theme, const QString &themeName) { if (theme->themeName() != themeName) { QSignalSpy spy(theme, SIGNAL(themeChanged())); theme->setThemeName(themeName); spy.wait(); } } // ------ Tests void IconItemTest::loadPixmap() { QScopedPointer item(createIconItem()); QPixmap sourcePixmap(QFINDTESTDATA("data/test_image.png")); item->setSize(sourcePixmap.size()); item->setProperty("source", sourcePixmap); QVERIFY(item->property("valid").toBool()); QImage capture = grabImage(item.data()); QCOMPARE(capture, sourcePixmap.toImage().convertToFormat(QImage::Format_ARGB32_Premultiplied)); QCOMPARE(sourcePixmap, item->property("source").value()); } //tests setting icon from a QImage void IconItemTest::loadImage() { QScopedPointer item(createIconItem()); QImage sourceImage(QFINDTESTDATA("data/test_image.png")); item->setSize(sourceImage.size()); item->setProperty("source", sourceImage); QVERIFY(item->property("valid").toBool()); QImage capture = grabImage(item.data()); QCOMPARE(capture, sourceImage.convertToFormat(QImage::Format_ARGB32_Premultiplied)); QCOMPARE(sourceImage, item->property("source").value()); } void IconItemTest::invalidIcon() { QString name("tst-plasma-framework-invalid-icon-name"); KIconLoader iconLoader("tst_plasma-framework"); if (iconLoader.hasIcon(name)) { QSKIP("Current icon theme has 'tst-plasma-framework-invalid-icon-name' icon."); } QQuickItem *item = createIconItem(); item->setProperty("source", name); QVERIFY(!item->property("valid").toBool()); QVERIFY(imageIsEmpty(grabImage(item))); } void IconItemTest::usesPlasmaTheme() { // usesPlasmaTheme = true (default) QQuickItem *item1 = createIconItem(); item1->setProperty("source", "konversation"); QVERIFY(item1->property("valid").toBool()); QCOMPARE(QStringLiteral("konversation"), item1->property("source").toString()); Plasma::Svg svg; svg.setContainsMultipleImages(true); svg.setImagePath("icons/konversation"); QImage img1 = grabImage(item1); QImage img2 = svg.image(QSize(item1->width(), item1->height()), "konversation"); QVERIFY(!imageIsEmpty(img1)); QVERIFY(!imageIsEmpty(img2)); QCOMPARE(img1, img2); // usesPlasmaTheme = false QQuickItem *item2 = createIconItem(); item2->setProperty("usesPlasmaTheme", false); item2->setProperty("source", "konversation"); img1 = grabImage(item2); // This depends on konversation icon being different in Plasma Breeze theme // and our test icon theme QVERIFY(img1 != img2); } void IconItemTest::animation() { // animated = true (default) QQuickItem *item1 = createIconItem(); item1->setProperty("source", "user-away"); // first icon is not animated QImage userAwayImg = grabImage(item1); item1->setProperty("source", "user-busy"); grabImage(item1); item1->setProperty("source", "user-away"); // animation from user-busy -> user-away QVERIFY(userAwayImg != grabImage(item1)); // animated = false QQuickItem *item2 = createIconItem(); item2->setProperty("animated", false); item2->setProperty("source", "user-busy"); QImage userBusyImg = grabImage(item2); item2->setProperty("source", "user-away"); QCOMPARE(userAwayImg, grabImage(item2)); item2->setProperty("source", "user-busy"); QCOMPARE(userBusyImg, grabImage(item2)); } void IconItemTest::animationAfterHide() { QQuickItem *item1 = createIconItem(); QQuickItem *item2 = createIconItem(); item1->setProperty("source", "user-away"); item2->setProperty("source", "user-busy"); // first icon is not animated QImage userAwayImg = grabImage(item1); QImage userBusyImg = grabImage(item2); item1->setProperty("source", "user-busy"); grabImage(item1); item1->setProperty("visible", "false"); item1->setProperty("visible", "true"); item1->setProperty("source", "user-away"); // icon was hidden, no animation QCOMPARE(userAwayImg, grabImage(item1)); item1->setProperty("source", "user-busy"); QVERIFY(userBusyImg != grabImage(item1)); } void IconItemTest::bug_359388() { if (!KIconTheme::list().contains("hicolor")) { // This test depends on hicolor icon theme to resolve the icon. QSKIP("hicolor icon theme not available"); } QString name("bug359388"); KIconLoader iconLoader("tst_plasma-framework"); QIcon customThemeIcon(new KIconEngine(name, &iconLoader)); if (iconLoader.hasIcon(name)) { QSKIP("Current icon theme has 'bug359388' icon."); } iconLoader.addAppDir("tst_plasma-framework", QFINDTESTDATA("data/bug359388")); QQuickItem *item1 = createIconItem(); item1->setProperty("source", customThemeIcon); QVERIFY(item1->property("valid").toBool()); QCOMPARE(customThemeIcon, item1->property("source").value()); QQuickItem *item2 = createIconItem(); item2->setProperty("source", QIcon(QFINDTESTDATA("data/bug359388/hicolor/22x22/apps/" + name + ".svg"))); QVERIFY(item2->property("valid").toBool()); QCOMPARE(grabImage(item1), grabImage(item2)); } void IconItemTest::loadSvg() { QString name("tst-plasma-framework-test-icon"); QQuickItem *item = createIconItem(); item->setProperty("animated", false); item->setSize(QSize(22, 22)); item->setProperty("source", name); QVERIFY(item->property("valid").toBool()); Plasma::Svg *svg; svg = findPlasmaSvg(item); Q_ASSERT(svg); QCOMPARE(svg->imagePath(), QFINDTESTDATA("data/icons/test-theme/apps/22/" + name + ".svg")); // we only have 32x32 and 22x22 version in the theme, thus 32x32 is a better match. item->setSize(QSize(64, 64)); // just to update the icon grabImage(item); svg = findPlasmaSvg(item); Q_ASSERT(svg); QCOMPARE(svg->imagePath(), QFINDTESTDATA("data/icons/test-theme/apps/32/" + name + ".svg")); } void IconItemTest::themeChange() { // Icon from Plasma theme QQuickItem *item1 = createIconItem(); item1->setProperty("animated", false); item1->setProperty("source", "zoom-fit-height"); Plasma::Svg *svg1 = item1->findChild(); changeTheme(svg1->theme(), "breeze-light"); QImage img1 = grabImage(item1); changeTheme(svg1->theme(), "breeze-dark"); QImage img2 = grabImage(item1); QVERIFY(img1 != img2); // Icon from icon theme QQuickItem *item2 = createIconItem(); item2->setProperty("animated", false); item2->setProperty("width", 22); item2->setProperty("height", 22); item2->setProperty("source", "tst-plasma-framework-test-icon"); Plasma::Svg *svg2 = item2->findChild(); changeTheme(svg2->theme(), "breeze-light"); img1 = grabImage(item2); changeTheme(svg2->theme(), "breeze-dark"); img2 = grabImage(item2); QVERIFY(img1 != img2); } void IconItemTest::qiconFromTheme() { // Icon from Plasma theme QQuickItem *item1 = createIconItem(); QIcon icon1 = QIcon::fromTheme("konversation"); item1->setProperty("source", icon1); QVERIFY(item1->findChild()); QVERIFY(!imageIsEmpty(grabImage(item1))); QCOMPARE(icon1, item1->property("source").value()); // Icon from icon theme QQuickItem *item2 = createIconItem(); QIcon icon2 = QIcon::fromTheme("tst-plasma-framework-test-icon"); item2->setProperty("source", icon2); QVERIFY(item2->findChild()); QVERIFY(!imageIsEmpty(grabImage(item2))); QCOMPARE(icon2, item2->property("source").value()); } void IconItemTest::changeColorGroup() { // Icon from Plasma theme QQuickItem *item = createIconItem(); item->setProperty("animated", false); item->setProperty("source", "zoom-fit-height"); Plasma::Svg *svg = item->findChild(); // not using "breeze" theme as that one follows system color scheme // and that one might not have a complementary group or a broken one changeTheme(svg->theme(), "breeze-light"); QSignalSpy spy(svg, SIGNAL(repaintNeeded())); QVERIFY(spy.isValid()); QImage img1 = grabImage(item); item->setProperty("colorGroup", Plasma::Theme::ComplementaryColorGroup); QTRY_VERIFY(spy.count() == 1); QImage img2 = grabImage(item); QVERIFY(img1 != img2); } void IconItemTest::animatingActiveChange() { QQuickItem *item1 = createIconItem(); item1->setProperty("animated", false); item1->setProperty("source", "tst-plasma-framework-test-icon"); QImage img1 = grabImage(item1); QQuickItem *item2 = createIconItem(); item2->setProperty("animated", false); item2->setProperty("active", true); item2->setProperty("source", "tst-plasma-framework-test-icon"); QImage img2 = grabImage(item2); QVERIFY(img1 != img2); item1->setProperty("active", true); img1 = grabImage(item1); QVERIFY(img1 != img2); // animation is running } void IconItemTest::animatingEnabledChange() { QQuickItem *item1 = createIconItem(); item1->setProperty("animated", false); item1->setProperty("source", "tst-plasma-framework-test-icon"); QImage img1 = grabImage(item1); QQuickItem *item2 = createIconItem(); item2->setProperty("animated", false); item2->setProperty("enabled", false); item2->setProperty("source", "tst-plasma-framework-test-icon"); QImage img2 = grabImage(item2); QVERIFY(img1 != img2); item1->setProperty("enabled", false); img1 = grabImage(item1); QVERIFY(img1 != img2); // animation is running } void IconItemTest::windowChanged() { QQuickItem *item = createIconItem(); item->setProperty("animated", false); item->setProperty("source", "tst-plasma-framework-test-icon"); QImage img = grabImage(item); QQuickView newView; newView.setSource(QUrl::fromLocalFile(QFINDTESTDATA("data/view.qml"))); newView.show(); QTest::qWaitForWindowExposed(&newView); item->setProperty("visible", false); item->setParentItem(newView.rootObject()); item->setProperty("visible", true); QCOMPARE(grabImage(item), img); } void IconItemTest::paintedSize() { QQuickItem *item = createIconItem(); QCOMPARE(item->property("paintedWidth").toInt(), item->property("implicitWidth").toInt()); QCOMPARE(item->property("paintedHeight").toInt(), item->property("implicitHeight").toInt()); item->setWidth(40); item->setHeight(40); QCOMPARE(item->property("paintedWidth").toInt(), 32); QCOMPARE(item->property("paintedHeight").toInt(), 32); QIcon landscapeIcon(QPixmap(40, 35)); item->setProperty("source", landscapeIcon); grabImage(item); // basically just to force loading the pixmap // expanded to fit IconItem size whilst keeping aspect ratio // width should be rounded to icon size, ie. 32 is next smallest QCOMPARE(item->property("paintedWidth").toInt(), 32); // height should still match aspect ratio, so *not* 24! QCOMPARE(item->property("paintedHeight").toInt(), 28); QIcon portraitIcon(QPixmap(15, 40)); item->setProperty("source", portraitIcon); grabImage(item); QCOMPARE(item->property("paintedWidth").toInt(), 12); QCOMPARE(item->property("paintedHeight").toInt(), 32); item->setWidth(400); item->setHeight(400); grabImage(item); QCOMPARE(item->property("paintedWidth").toInt(), 150); QCOMPARE(item->property("paintedHeight").toInt(), 400); } void IconItemTest::implicitSize() { KConfigGroup cg(KSharedConfig::openConfig(), "DialogIcons"); cg.writeEntry("Size", 22); cg.sync(); KIconLoader::global()->reconfigure(QString()); QQuickItem *item = createIconItem(); // qreal cast needed as QTest::qCompare fails to link QCOMPARE(item->implicitWidth(), qreal(22)); QCOMPARE(item->implicitHeight(), qreal(22)); QSignalSpy widthSpy(item, &QQuickItem::implicitWidthChanged); QVERIFY(widthSpy.isValid()); QSignalSpy heightSpy(item, &QQuickItem::implicitHeightChanged); QVERIFY(heightSpy.isValid()); cg.writeEntry("Size", 64); cg.sync(); KIconLoader::global()->reconfigure(QString()); // merely changing the setting and calling reconfigure won't emit this signal, // the KCM uses a method "newIconLoader" method which does that but it's deprecated emit KIconLoader::global()->iconLoaderSettingsChanged(); QCOMPARE(widthSpy.count(), 1); QCOMPARE(heightSpy.count(), 1); QCOMPARE(item->implicitWidth(), qreal(64)); QCOMPARE(item->implicitHeight(), qreal(64)); } +void IconItemTest::roundToIconSize() +{ + QQuickItem *item = createIconItem(); + + item->setWidth(25); + item->setHeight(25); + QVERIFY(item->property("paintedWidth").toInt() != 25); + QVERIFY(item->property("paintedHeight").toInt() != 25); + + QSignalSpy paintedSizeSpy(item, SIGNAL(paintedSizeChanged())); + QSignalSpy roundToIconSizeSpy(item, SIGNAL(roundToIconSizeChanged())); + + item->setProperty("roundToIconSize", false); + + QTRY_COMPARE(paintedSizeSpy.count(), 1); + QTRY_COMPARE(roundToIconSizeSpy.count(), 1); + QVERIFY(item->property("paintedWidth").toInt() == 25); + QVERIFY(item->property("paintedHeight").toInt() == 25); +} + QTEST_MAIN(IconItemTest) diff --git a/autotests/iconitemtest.h b/autotests/iconitemtest.h index fcf7003a5..842682932 100644 --- a/autotests/iconitemtest.h +++ b/autotests/iconitemtest.h @@ -1,68 +1,69 @@ /****************************************************************************** * Copyright 2016 David Rosca * * * * This library is free software; you can redistribute it and/or * * modify it under the terms of the GNU Library General Public * * License as published by the Free Software Foundation; either * * version 2 of the License, or (at your option) any later version. * * * * This library is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * Library General Public License for more details. * * * * You should have received a copy of the GNU Library General Public License * * along with this library; see the file COPYING.LIB. If not, write to * * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301, USA. * *******************************************************************************/ #pragma once #include #include #include namespace Plasma { class Svg; class Theme; } class IconItemTest : public QObject { Q_OBJECT private Q_SLOTS: void initTestCase(); void cleanupTestCase(); void init(); void cleanup(); void loadPixmap(); void loadImage(); void invalidIcon(); void usesPlasmaTheme(); void animation(); void animationAfterHide(); void bug_359388(); void loadSvg(); void themeChange(); void qiconFromTheme(); void changeColorGroup(); void animatingActiveChange(); void animatingEnabledChange(); void windowChanged(); void paintedSize(); void implicitSize(); + void roundToIconSize(); private: QQuickItem *createIconItem(); QImage grabImage(QQuickItem *item); Plasma::Svg *findPlasmaSvg(QQuickItem *item); void changeTheme(Plasma::Theme *theme, const QString &themeName); QQuickView *m_view; }; diff --git a/src/declarativeimports/core/iconitem.cpp b/src/declarativeimports/core/iconitem.cpp index 4b2480834..1fa13bb14 100644 --- a/src/declarativeimports/core/iconitem.cpp +++ b/src/declarativeimports/core/iconitem.cpp @@ -1,573 +1,604 @@ /* * Copyright 2012 Marco Martin * Copyright 2014 David Edmundson * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details * * You should have received a copy of the GNU Library General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "iconitem.h" #include #include #include #include #include #include #include #include #include #include #include #include "fadingnode_p.h" #include #include "units.h" IconItem::IconItem(QQuickItem *parent) : QQuickItem(parent), m_svgIcon(0), m_status(Plasma::Svg::Normal), m_smooth(false), m_active(false), m_animated(true), m_usesPlasmaTheme(true), + m_roundToIconSize(true), m_textureChanged(false), m_sizeChanged(false), m_allowNextAnimation(false), m_blockNextAnimation(false), m_colorGroup(Plasma::Theme::NormalColorGroup), m_animValue(0) { m_animation = new QPropertyAnimation(this); connect(m_animation, SIGNAL(valueChanged(QVariant)), this, SLOT(valueChanged(QVariant))); connect(m_animation, SIGNAL(finished()), this, SLOT(animationFinished())); m_animation->setTargetObject(this); m_animation->setEasingCurve(QEasingCurve::InOutQuad); m_animation->setDuration(250); //FIXME from theme setFlag(ItemHasContents, true); connect(KIconLoader::global(), &KIconLoader::iconLoaderSettingsChanged, this, &IconItem::updateImplicitSize); connect(this, &QQuickItem::enabledChanged, this, &IconItem::enabledChanged); connect(this, &QQuickItem::windowChanged, this, &IconItem::schedulePixmapUpdate); connect(this, SIGNAL(overlaysChanged()), this, SLOT(schedulePixmapUpdate())); updateImplicitSize(); } IconItem::~IconItem() { } void IconItem::updateImplicitSize() { //initialize implicit size to the Dialog size const int implicitSize = KIconLoader::global()->currentSize(KIconLoader::Dialog); setImplicitSize(implicitSize, implicitSize); } void IconItem::setSource(const QVariant &source) { if (source == m_source) { return; } m_source = source; QString sourceString = source.toString(); // If the QIcon was created with QIcon::fromTheme(), try to load it as svg if (source.canConvert() && !source.value().name().isEmpty()) { sourceString = source.value().name(); } if (!sourceString.isEmpty()) { //If a url in the form file:// is passed, take the image pointed by that from disk QUrl url(sourceString); if (url.isLocalFile()) { m_icon = QIcon(); m_imageIcon = QImage(url.path()); m_svgIconName.clear(); delete m_svgIcon; m_svgIcon = 0; } else { if (!m_svgIcon) { m_svgIcon = new Plasma::Svg(this); m_svgIcon->setColorGroup(m_colorGroup); m_svgIcon->setStatus(m_status); m_svgIcon->setDevicePixelRatio((window() ? window()->devicePixelRatio() : qApp->devicePixelRatio())); connect(m_svgIcon, &Plasma::Svg::repaintNeeded, this, &IconItem::schedulePixmapUpdate); } if (m_usesPlasmaTheme) { //try as a svg icon from plasma theme m_svgIcon->setImagePath(QLatin1String("icons/") + sourceString.split('-').first()); m_svgIcon->setContainsMultipleImages(true); } //success? if (m_svgIcon->isValid() && m_svgIcon->hasElement(sourceString)) { m_icon = QIcon(); m_svgIconName = sourceString; //ok, svg not available from the plasma theme } else { //try to load from iconloader an svg with Plasma::Svg const auto *iconTheme = KIconLoader::global()->theme(); QString iconPath; if (iconTheme) { iconPath = iconTheme->iconPath(sourceString + QLatin1String(".svg"), qMin(width(), height()), KIconLoader::MatchBest); if (iconPath.isEmpty()) { iconPath = iconTheme->iconPath(sourceString + QLatin1String(".svgz"), qMin(width(), height()), KIconLoader::MatchBest); } } else { qWarning() << "KIconLoader has no theme set"; } if (!iconPath.isEmpty()) { m_svgIcon->setImagePath(iconPath); m_svgIconName = sourceString; //fail, use QIcon } else { //if we started with a QIcon use that. m_icon = source.value(); if (m_icon.isNull()) { m_icon = QIcon::fromTheme(sourceString); } m_svgIconName.clear(); delete m_svgIcon; m_svgIcon = 0; m_imageIcon = QImage(); } } } } else if (source.canConvert()) { m_icon = source.value(); m_imageIcon = QImage(); m_svgIconName.clear(); delete m_svgIcon; m_svgIcon = 0; } else if (source.canConvert()) { m_icon = QIcon(); m_imageIcon = source.value(); m_svgIconName.clear(); delete m_svgIcon; m_svgIcon = 0; } else { m_icon = QIcon(); m_imageIcon = QImage(); m_svgIconName.clear(); delete m_svgIcon; m_svgIcon = 0; } if (width() > 0 && height() > 0) { schedulePixmapUpdate(); } emit sourceChanged(); emit validChanged(); } QVariant IconItem::source() const { return m_source; } void IconItem::setColorGroup(Plasma::Theme::ColorGroup group) { if (m_colorGroup == group) { return; } m_colorGroup = group; if (m_svgIcon) { m_svgIcon->setColorGroup(group); } emit colorGroupChanged(); } Plasma::Theme::ColorGroup IconItem::colorGroup() const { return m_colorGroup; } void IconItem::setOverlays(const QStringList &overlays) { if (overlays == m_overlays) { return; } m_overlays = overlays; emit overlaysChanged(); } QStringList IconItem::overlays() const { return m_overlays; } bool IconItem::isActive() const { return m_active; } void IconItem::setActive(bool active) { if (m_active == active) { return; } m_active = active; if (isComponentComplete()) { m_allowNextAnimation = true; schedulePixmapUpdate(); } emit activeChanged(); } void IconItem::setSmooth(const bool smooth) { if (smooth == m_smooth) { return; } m_smooth = smooth; update(); } bool IconItem::smooth() const { return m_smooth; } bool IconItem::isAnimated() const { return m_animated; } void IconItem::setAnimated(bool animated) { if (m_animated == animated) { return; } m_animated = animated; emit animatedChanged(); } bool IconItem::usesPlasmaTheme() const { return m_usesPlasmaTheme; } void IconItem::setUsesPlasmaTheme(bool usesPlasmaTheme) { if (m_usesPlasmaTheme == usesPlasmaTheme) { return; } m_usesPlasmaTheme = usesPlasmaTheme; // Reload icon with new settings if (m_svgIcon && m_svgIcon->hasElement(m_source.toString())) { const QVariant src = m_source; m_source.clear(); setSource(src); } emit usesPlasmaThemeChanged(); } +bool IconItem::roundToIconSize() const +{ + return m_roundToIconSize; +} + +void IconItem::setRoundToIconSize(bool roundToIconSize) +{ + if (m_roundToIconSize == roundToIconSize) { + return; + } + + const QSize oldPaintedSize = paintedSize(); + + m_roundToIconSize = roundToIconSize; + emit roundToIconSizeChanged(); + + if (oldPaintedSize != paintedSize()) { + emit paintedSizeChanged(); + } + + schedulePixmapUpdate(); +} + bool IconItem::isValid() const { return !m_icon.isNull() || m_svgIcon || !m_imageIcon.isNull(); } int IconItem::paintedWidth() const { return paintedSize(boundingRect().size()).width(); } int IconItem::paintedHeight() const { return paintedSize(boundingRect().size()).height(); } QSize IconItem::paintedSize(const QSizeF &containerSize) const { const QSize &actualContainerSize = (containerSize.isValid() ? containerSize : boundingRect().size()).toSize(); const QSize paintedSize = m_iconPixmap.size().scaled(actualContainerSize, Qt::KeepAspectRatio); const int width = paintedSize.width(); const int height = paintedSize.height(); if (width == height) { - return QSize(Units::roundToIconSize(width), Units::roundToIconSize(height)); + if (m_roundToIconSize) { + return QSize(Units::roundToIconSize(width), Units::roundToIconSize(height)); + } else { + return QSize(width, height); + } } // if we don't have a square image, we still want it to be rounded to icon size // but we cannot just blindly round both as we might erroneously change a 50x45 image to be 48x32 // instead, round the bigger of the two and then downscale the smaller with the ratio if (width > height) { - const int roundedWidth = Units::roundToIconSize(width); + const int roundedWidth = m_roundToIconSize ? Units::roundToIconSize(width) : width; return QSize(roundedWidth, qRound(height * (roundedWidth / static_cast(width)))); } else { - const int roundedHeight = Units::roundToIconSize(height); + const int roundedHeight = m_roundToIconSize ? Units::roundToIconSize(height) : height; return QSize(qRound(width * (roundedHeight / static_cast(height))), roundedHeight); } } void IconItem::setStatus(Plasma::Svg::Status status) { if (m_status == status) { return; } m_status = status; if (m_svgIcon) { m_svgIcon->setStatus(status); } emit statusChanged(); } Plasma::Svg::Status IconItem::status() const { return m_status; } void IconItem::updatePolish() { QQuickItem::updatePolish(); loadPixmap(); } QSGNode* IconItem::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *updatePaintNodeData) { Q_UNUSED(updatePaintNodeData) if (m_iconPixmap.isNull() || width() == 0 || height() == 0) { delete oldNode; return nullptr; } if (m_animation->state() == QAbstractAnimation::Running) { FadingNode *animatingNode = dynamic_cast(oldNode); if (!animatingNode || m_textureChanged) { delete oldNode; QSGTexture *source = window()->createTextureFromImage(m_iconPixmap.toImage()); QSGTexture *target = window()->createTextureFromImage(m_oldIconPixmap.toImage()); animatingNode = new FadingNode(source, target); m_sizeChanged = true; m_textureChanged = false; } animatingNode->setProgress(m_animValue); if (m_sizeChanged) { const QSize newSize = paintedSize(); const QRect destRect(QPointF(boundingRect().center() - QPointF(newSize.width(), newSize.height()) / 2).toPoint(), newSize); animatingNode->setRect(destRect); m_sizeChanged = false; } return animatingNode; } else { ManagedTextureNode *textureNode = dynamic_cast(oldNode); if (!textureNode || m_textureChanged) { delete oldNode; textureNode = new ManagedTextureNode; textureNode->setTexture(QSharedPointer(window()->createTextureFromImage(m_iconPixmap.toImage(), QQuickWindow::TextureCanUseAtlas))); m_sizeChanged = true; m_textureChanged = false; } if (m_sizeChanged) { const QSize newSize = paintedSize(); const QRect destRect(QPointF(boundingRect().center() - QPointF(newSize.width(), newSize.height()) / 2).toPoint(), newSize); textureNode->setRect(destRect); m_sizeChanged = false; } return textureNode; } } void IconItem::valueChanged(const QVariant &value) { m_animValue = value.toReal(); update(); } void IconItem::enabledChanged() { m_allowNextAnimation = true; schedulePixmapUpdate(); } void IconItem::animationFinished() { m_oldIconPixmap = QPixmap(); m_textureChanged = true; update(); } void IconItem::schedulePixmapUpdate() { polish(); } void IconItem::loadPixmap() { if (!isComponentComplete()) { return; } - const int size = Units::roundToIconSize(qMin(qRound(width()), qRound(height()))); + int size = qMin(qRound(width()), qRound(height())); + if (m_roundToIconSize) { + size = Units::roundToIconSize(size); + } //final pixmap to paint QPixmap result; if (size <= 0) { m_iconPixmap = QPixmap(); m_animation->stop(); update(); return; } else if (m_svgIcon) { m_svgIcon->resize(size, size); if (m_svgIcon->hasElement(m_svgIconName)) { result = m_svgIcon->pixmap(m_svgIconName); } else if (!m_svgIconName.isEmpty()) { const auto *iconTheme = KIconLoader::global()->theme(); QString iconPath; if (iconTheme) { iconPath = iconTheme->iconPath(m_svgIconName + QLatin1String(".svg"), qMin(width(), height()), KIconLoader::MatchBest); if (iconPath.isEmpty()) { iconPath = iconTheme->iconPath(m_svgIconName + QLatin1String(".svgz"), qMin(width(), height()), KIconLoader::MatchBest); } } else { qWarning() << "KIconLoader has no theme set"; } if (!iconPath.isEmpty()) { m_svgIcon->setImagePath(iconPath); } result = m_svgIcon->pixmap(); } } else if (!m_icon.isNull()) { result = m_icon.pixmap(QSize(size, size) * (window() ? window()->devicePixelRatio() : qApp->devicePixelRatio())); } else if (!m_imageIcon.isNull()) { result = QPixmap::fromImage(m_imageIcon); } else { m_iconPixmap = QPixmap(); m_animation->stop(); update(); return; } // Strangely KFileItem::overlays() returns empty string-values, so // we need to check first whether an overlay must be drawn at all. // It is more efficient to do it here, as KIconLoader::drawOverlays() // assumes that an overlay will be drawn and has some additional // setup time. foreach (const QString& overlay, m_overlays) { if (!overlay.isEmpty()) { // There is at least one overlay, draw all overlays above m_pixmap // and cancel the check KIconLoader::global()->drawOverlays(m_overlays, result, KIconLoader::Desktop); break; } } if (!isEnabled()) { result = KIconLoader::global()->iconEffect()->apply(result, KIconLoader::Desktop, KIconLoader::DisabledState); } else if (m_active) { result = KIconLoader::global()->iconEffect()->apply(result, KIconLoader::Desktop, KIconLoader::ActiveState); } const QSize oldPaintedSize = paintedSize(); m_oldIconPixmap = m_iconPixmap; m_iconPixmap = result; m_textureChanged = true; if (oldPaintedSize != paintedSize()) { emit paintedSizeChanged(); } //don't animate initial setting if ((m_animated || m_allowNextAnimation) && !m_oldIconPixmap.isNull() && !m_sizeChanged && !m_blockNextAnimation) { m_animValue = 0.0; m_animation->setStartValue((qreal)0); m_animation->setEndValue((qreal)1); m_animation->start(); m_allowNextAnimation = false; } else { m_animValue = 1.0; m_animation->stop(); m_blockNextAnimation = false; } update(); } void IconItem::itemChange(ItemChange change, const ItemChangeData &value) { if (change == ItemVisibleHasChanged && value.boolValue) { m_blockNextAnimation = true; } QQuickItem::itemChange(change, value); } void IconItem::geometryChanged(const QRectF &newGeometry, const QRectF &oldGeometry) { if (newGeometry.size() != oldGeometry.size()) { m_sizeChanged = true; if (newGeometry.width() > 0 && newGeometry.height() > 0) { schedulePixmapUpdate(); } else { update(); } if (paintedSize(oldGeometry.size()) != paintedSize(newGeometry.size())) { emit paintedSizeChanged(); } } QQuickItem::geometryChanged(newGeometry, oldGeometry); } void IconItem::componentComplete() { QQuickItem::componentComplete(); schedulePixmapUpdate(); } diff --git a/src/declarativeimports/core/iconitem.h b/src/declarativeimports/core/iconitem.h index f30d0473d..e4b2a959d 100644 --- a/src/declarativeimports/core/iconitem.h +++ b/src/declarativeimports/core/iconitem.h @@ -1,216 +1,226 @@ /* * Copyright 2012 Marco Martin * Copyright 2014 David Edmundson * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details * * You should have received a copy of the GNU Library General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef ICONITEM_H #define ICONITEM_H #include #include #include #include #include #include class QPropertyAnimation; /** * @class IconItem * @short Displays an icon, either from the standard QIcon system or where applicable from the theme SVG files */ class IconItem : public QQuickItem { Q_OBJECT /** * Sets the icon to be displayed. Source can be one of: * - iconName (as a string) * - URL * - QImage * - QPixmap * - QIcon * * When passing an icon name (or a QIcon with an icon name set) it will: * - load the plasma variant if usesPlasmaTheme is set and exists * - otherwise try to load the icon as an SVG so colorscopes apply * - load the icon as normal */ Q_PROPERTY(QVariant source READ source WRITE setSource NOTIFY sourceChanged) /** * Specifies the color group to use for this icon * This only applies to icons loaded from the plasma theme */ Q_PROPERTY(Plasma::Theme::ColorGroup colorGroup READ colorGroup WRITE setColorGroup NOTIFY colorGroupChanged) /** * Specifies the overlay(s) for this icon */ Q_PROPERTY(QStringList overlays READ overlays WRITE setOverlays NOTIFY overlaysChanged) /** * See QQuickItem::smooth */ Q_PROPERTY(bool smooth READ smooth WRITE setSmooth NOTIFY smoothChanged) /** * Apply a visual indication that this icon is active. * Typically used to indicate that it is hovered */ Q_PROPERTY(bool active READ isActive WRITE setActive NOTIFY activeChanged) /** * Sets the image in a selected status. * Svgs can be colored with system color themes, if the status is selected, * the TextColor will become HighlightedText color and BackgroundColor * will become HighlightColor, making the svg graphics (for instance an icon) * will look correct together selected text * @see Plasma::Svg::status * @since 5.23 */ Q_PROPERTY(Plasma::Svg::Status status READ status WRITE setStatus NOTIFY statusChanged) /** * If set, icon will blend when the source is changed */ Q_PROPERTY(bool animated READ isAnimated WRITE setAnimated NOTIFY animatedChanged) /** * If set, icon will try and use icons from the Plasma theme if possible */ Q_PROPERTY(bool usesPlasmaTheme READ usesPlasmaTheme WRITE setUsesPlasmaTheme NOTIFY usesPlasmaThemeChanged) + /** + * If set, icon will round the painted size to defined icon sizes. Default is true. + */ + Q_PROPERTY(bool roundToIconSize READ roundToIconSize WRITE setRoundToIconSize NOTIFY roundToIconSizeChanged) + /** * True if a valid icon is set. False otherwise. */ Q_PROPERTY(bool valid READ isValid NOTIFY validChanged) /** * The width of the icon that is actually painted * Icons are drawn at standard icon sizes (eg. 16,32,64) centered within the item */ Q_PROPERTY(int paintedWidth READ paintedWidth NOTIFY paintedSizeChanged) /** * The height of the icon actually being drawn. * Icons are drawn at standard icon sizes (eg. 16,32,64) centered within the item */ Q_PROPERTY(int paintedHeight READ paintedHeight NOTIFY paintedSizeChanged) public: IconItem(QQuickItem *parent = 0); ~IconItem(); void setSource(const QVariant &source); QVariant source() const; void setColorGroup(Plasma::Theme::ColorGroup group); Plasma::Theme::ColorGroup colorGroup() const; void setOverlays(const QStringList &overlays); QStringList overlays() const; bool isActive() const; void setActive(bool active); void setSmooth(const bool smooth); bool smooth() const; bool isAnimated() const; void setAnimated(bool animated); bool usesPlasmaTheme() const; void setUsesPlasmaTheme(bool usesPlasmaTheme); + bool roundToIconSize() const; + void setRoundToIconSize(bool roundToIconSize); + bool isValid() const; int paintedWidth() const; int paintedHeight() const; void setStatus(Plasma::Svg::Status status); Plasma::Svg::Status status() const; void updatePolish() Q_DECL_OVERRIDE; QSGNode* updatePaintNode(QSGNode * oldNode, UpdatePaintNodeData * updatePaintNodeData) Q_DECL_OVERRIDE; void itemChange(ItemChange change, const ItemChangeData &value) Q_DECL_OVERRIDE; void geometryChanged(const QRectF &newGeometry, const QRectF &oldGeometry) Q_DECL_OVERRIDE; void componentComplete() Q_DECL_OVERRIDE; Q_SIGNALS: void overlaysChanged(); void activeChanged(); void sourceChanged(); void smoothChanged(); void animatedChanged(); void usesPlasmaThemeChanged(); + void roundToIconSizeChanged(); void validChanged(); void colorGroupChanged(); void paintedSizeChanged(); void statusChanged(); private Q_SLOTS: void schedulePixmapUpdate(); void animationFinished(); void valueChanged(const QVariant &value); void enabledChanged(); private: void loadPixmap(); QSize paintedSize(const QSizeF &containerSize = QSizeF()) const; void updateImplicitSize(); //all the ways we can set an source. Only one of them will be valid QIcon m_icon; Plasma::Svg *m_svgIcon; QString m_svgIconName; QPixmap m_pixmapIcon; QImage m_imageIcon; //this contains the raw variant it was passed QVariant m_source; Plasma::Svg::Status m_status; QSizeF m_implicitSize; bool m_smooth; bool m_active; bool m_animated; bool m_usesPlasmaTheme; + bool m_roundToIconSize; bool m_textureChanged; bool m_sizeChanged; bool m_allowNextAnimation; bool m_blockNextAnimation; QPixmap m_iconPixmap; QPixmap m_oldIconPixmap; QStringList m_overlays; Plasma::Theme::ColorGroup m_colorGroup; //animation on pixmap change QPropertyAnimation *m_animation; qreal m_animValue; }; #endif