/* * Copyright 2012 Marco Martin * * 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 QPixmap transition(const QPixmap &from, const QPixmap &to, qreal amount) { if (from.isNull() && to.isNull()) { return from; } if (qFuzzyCompare(amount + 1, qreal(1.0))) { return from; } QRect startRect(from.rect()); QRect targetRect(to.rect()); QSize pixmapSize = startRect.size().expandedTo(targetRect.size()); QRect toRect = QRect(QPoint(0,0), pixmapSize); targetRect.moveCenter(toRect.center()); startRect.moveCenter(toRect.center()); //paint to in the center of from QColor color; color.setAlphaF(amount); // If the native paint engine supports Porter/Duff compositing and CompositionMode_Plus QPaintEngine *paintEngine = from.paintEngine(); if (paintEngine && paintEngine->hasFeature(QPaintEngine::PorterDuff) && paintEngine->hasFeature(QPaintEngine::BlendModes)) { QPixmap startPixmap(pixmapSize); startPixmap.fill(Qt::transparent); QPixmap targetPixmap(pixmapSize); targetPixmap.fill(Qt::transparent); QPainter p; p.begin(&targetPixmap); p.drawPixmap(targetRect, to); p.setCompositionMode(QPainter::CompositionMode_DestinationIn); p.fillRect(targetRect, color); p.end(); p.begin(&startPixmap); p.drawPixmap(startRect, from); p.setCompositionMode(QPainter::CompositionMode_DestinationOut); p.fillRect(startRect, color); p.setCompositionMode(QPainter::CompositionMode_Plus); p.drawPixmap(targetRect, targetPixmap); p.end(); return startPixmap; } #warning Cannot use XRender with QPixmap anymore. Find equivalent with Qt API. #if 0 // HAVE_X11 && defined(HAVE_XRENDER) // We have Xrender support else if (paintEngine && paintEngine->hasFeature(QPaintEngine::PorterDuff)) { // QX11PaintEngine doesn't implement CompositionMode_Plus in Qt 4.3, // which we need to be able to do a transition from one pixmap to // another. // // In order to avoid the overhead of converting the pixmaps to images // and doing the operation entirely in software, this function has a // specialized path for X11 that uses Xrender directly to do the // transition. This operation can be fully accelerated in HW. // // This specialization can be removed when QX11PaintEngine supports // CompositionMode_Plus. QPixmap source(targetPixmap), destination(startPixmap); source.detach(); destination.detach(); Display *dpy = QX11Info::display(); XRenderPictFormat *format = XRenderFindStandardFormat(dpy, PictStandardA8); XRenderPictureAttributes pa; pa.repeat = 1; // RepeatNormal // Create a 1x1 8 bit repeating alpha picture Pixmap pixmap = XCreatePixmap(dpy, destination.handle(), 1, 1, 8); Picture alpha = XRenderCreatePicture(dpy, pixmap, format, CPRepeat, &pa); XFreePixmap(dpy, pixmap); // Fill the alpha picture with the opacity value XRenderColor xcolor; xcolor.alpha = quint16(0xffff * amount); XRenderFillRectangle(dpy, PictOpSrc, alpha, &xcolor, 0, 0, 1, 1); // Reduce the alpha of the destination with 1 - opacity XRenderComposite(dpy, PictOpOutReverse, alpha, None, destination.x11PictureHandle(), 0, 0, 0, 0, 0, 0, destination.width(), destination.height()); // Add source * opacity to the destination XRenderComposite(dpy, PictOpAdd, source.x11PictureHandle(), alpha, destination.x11PictureHandle(), toRect.x(), toRect.y(), 0, 0, 0, 0, destination.width(), destination.height()); XRenderFreePicture(dpy, alpha); return destination; } #endif else { // Fall back to using QRasterPaintEngine to do the transition. QImage under(pixmapSize, QImage::Format_ARGB32_Premultiplied); under.fill(Qt::transparent); QImage over(pixmapSize, QImage::Format_ARGB32_Premultiplied); over.fill(Qt::transparent); QPainter p; p.begin(&over); p.drawPixmap(targetRect, to); p.setCompositionMode(QPainter::CompositionMode_DestinationIn); p.fillRect(over.rect(), color); p.end(); p.begin(&under); p.drawPixmap(startRect, from); p.setCompositionMode(QPainter::CompositionMode_DestinationOut); p.fillRect(startRect, color); p.setCompositionMode(QPainter::CompositionMode_Plus); p.drawImage(toRect.topLeft(), over); p.end(); return QPixmap::fromImage(under); } } IconItem::IconItem(QQuickItem *parent) : QQuickPaintedItem(parent), m_svgIcon(0), m_smooth(false), m_active(false), m_animValue(0) { m_loadPixmapTimer.setSingleShot(true); m_loadPixmapTimer.setInterval(150); connect(&m_loadPixmapTimer, &QTimer::timeout, [=] () { loadPixmap(); }); 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); setFlag(ItemHasContents, true); connect(KIconLoader::global(), SIGNAL(iconLoaderSettingsChanged()), this, SIGNAL(implicitWidthChanged())); connect(KIconLoader::global(), SIGNAL(iconLoaderSettingsChanged()), this, SIGNAL(implicitHeightChanged())); connect(this, SIGNAL(enabledChanged()), &m_loadPixmapTimer, SLOT(start())); //initialize implicit size to the Dialog size setImplicitWidth(KIconLoader::global()->currentSize(KIconLoader::Dialog)); setImplicitHeight(KIconLoader::global()->currentSize(KIconLoader::Dialog)); } IconItem::~IconItem() { } void IconItem::setSource(const QVariant &source) { if (source == m_source) { return; } m_source = source; if (source.canConvert()) { m_icon = source.value(); m_imageIcon = QImage(); m_pixmapIcon = QPixmap(); delete m_svgIcon; m_svgIcon = 0; } else if (source.canConvert()) { if (!m_svgIcon) { m_svgIcon = new Plasma::Svg(this); } //try as a svg icon m_svgIcon->setImagePath("icons/" + source.toString().split("-").first()); m_svgIcon->setContainsMultipleImages(true); //success? if (m_svgIcon->isValid() && m_svgIcon->hasElement(m_source.toString())) { m_icon = QIcon(); connect(m_svgIcon, SIGNAL(repaintNeeded()), this, SLOT(loadPixmap())); //ok, svg not available } else { m_icon = QIcon::fromTheme(source.toString()); delete m_svgIcon; m_svgIcon = 0; } m_imageIcon = QImage(); m_pixmapIcon = QPixmap(); } else if (source.canConvert()) { m_icon = QIcon(); m_imageIcon = QImage(); m_pixmapIcon = source.value(); delete m_svgIcon; m_svgIcon = 0; } else if (source.canConvert()) { m_icon = QIcon(); m_imageIcon = source.value(); m_pixmapIcon = QPixmap(); delete m_svgIcon; m_svgIcon = 0; } else { m_icon = QIcon(); m_imageIcon = QImage(); m_pixmapIcon = QPixmap(); delete m_svgIcon; m_svgIcon = 0; } if (width() > 0 && height() > 0) { m_loadPixmapTimer.start(); } emit sourceChanged(); emit validChanged(); } QVariant IconItem::source() const { return m_source; } bool IconItem::isActive() const { return m_active; } void IconItem::setActive(bool active) { if (m_active == active) { return; } m_active = active; m_loadPixmapTimer.start(); 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::isValid() const { return !m_icon.isNull() || m_svgIcon || !m_pixmapIcon.isNull() || !m_imageIcon.isNull(); } void IconItem::paint(QPainter *painter) { if (m_iconPixmaps.isEmpty()) { return; } painter->save(); painter->setRenderHint(QPainter::Antialiasing, m_smooth); painter->setRenderHint(QPainter::SmoothPixmapTransform, m_smooth); const int iconSize = adjustedSize(qMin(boundingRect().size().width(), boundingRect().size().height())); const QRect destRect(QPointF(boundingRect().center() - QPointF(iconSize/2, iconSize/2)).toPoint(), QSize(iconSize, iconSize)); if (m_animation->state() == QAbstractAnimation::Running) { QPixmap result = m_iconPixmaps.first(); result = transition(result, m_iconPixmaps.last(), m_animValue); painter->drawPixmap(destRect, result); //simpler logic for just paint } else { painter->drawPixmap(destRect, m_iconPixmaps.first()); } painter->restore(); } void IconItem::animationFinished() { while (m_iconPixmaps.count() > 1) { m_iconPixmaps.pop_front(); } } void IconItem::valueChanged(const QVariant &value) { m_animValue = value.toReal(); update(); } int IconItem::adjustedSize(int size) { //FIXME: Heuristic: allow 24x24 for icons/ that are in the systray(ugly) if (m_svgIcon && m_svgIcon->imagePath().contains("icons/") && size > KIconLoader::SizeSmallMedium && size < KIconLoader::SizeMedium) { return 24; //if size is less than 16, leave as is } else if (size < KIconLoader::SizeSmall) { //do nothing } else if (size < KIconLoader::SizeSmallMedium) { return KIconLoader::SizeSmall; } else if (size < KIconLoader::SizeMedium) { return KIconLoader::SizeSmallMedium; } else if (size < KIconLoader::SizeLarge) { return KIconLoader::SizeMedium; } else if (size < KIconLoader::SizeHuge) { return KIconLoader::SizeLarge; //if size is more than 64, leave as is } return size; } void IconItem::loadPixmap() { const int size = adjustedSize(qMin(width(), height())); //final pixmap to paint QPixmap result; if (size<=0) { //m_iconPixmaps.clear(); m_animation->stop(); update(); return; } else if (m_svgIcon) { m_svgIcon->resize(size, size); result = m_svgIcon->pixmap(m_source.toString()); } else if (!m_source.isNull()) { result = m_icon.pixmap(QSize(size, size)); } else if (!m_pixmapIcon.isNull()) { result = m_pixmapIcon; } else if (!m_imageIcon.isNull()) { result = QPixmap::fromImage(m_imageIcon); } else { m_iconPixmaps.clear(); m_animation->stop(); update(); return; } 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); } //this happen only when loadPixmap has been called when an anim is running while (m_iconPixmaps.count() > 1) { m_iconPixmaps.pop_front(); } m_iconPixmaps << result; //if there is only one image, don't animate //if an animation was already running, immediate transition, to not overload if (m_iconPixmaps.first().size() != result.size()) { m_animation->stop(); if (m_iconPixmaps.count() > 1) { m_iconPixmaps.pop_front(); } } else if (m_animation->state() == QAbstractAnimation::Running) { m_animation->stop(); m_iconPixmaps.pop_front(); } else if (m_iconPixmaps.count() > 1) { m_animation->setStartValue((qreal)0); m_animation->setEndValue((qreal)1); m_animation->start(); } update(); } void IconItem::geometryChanged(const QRectF &newGeometry, const QRectF &oldGeometry) { if (newGeometry.size() != oldGeometry.size()) { if (newGeometry.width() > 0 && newGeometry.height() > 0) { m_loadPixmapTimer.start(); update(); } } QQuickItem::geometryChanged(newGeometry, oldGeometry); } #include "iconitem.moc"