File indexing completed on 2024-11-10 04:56:47

0001 /*
0002     SPDX-FileCopyrightText: 2014 Martin Gräßlin <mgraesslin@kde.org>
0003 
0004     SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
0005 */
0006 #include "previewitem.h"
0007 #include "previewbridge.h"
0008 #include "previewclient.h"
0009 #include "previewsettings.h"
0010 #include <KDecoration2/DecoratedClient>
0011 #include <KDecoration2/Decoration>
0012 #include <KDecoration2/DecorationSettings>
0013 #include <KDecoration2/DecorationShadow>
0014 #include <QCoreApplication>
0015 #include <QCursor>
0016 #include <QPainter>
0017 #include <QQmlContext>
0018 #include <QQmlEngine>
0019 
0020 #include <cmath>
0021 
0022 #include <QDebug>
0023 
0024 namespace KDecoration2
0025 {
0026 namespace Preview
0027 {
0028 
0029 PreviewItem::PreviewItem(QQuickItem *parent)
0030     : QQuickPaintedItem(parent)
0031     , m_decoration(nullptr)
0032     , m_windowColor(QPalette().window().color())
0033 {
0034     setAcceptHoverEvents(true);
0035     setAcceptedMouseButtons(Qt::AllButtons);
0036     connect(this, &PreviewItem::widthChanged, this, &PreviewItem::syncSize);
0037     connect(this, &PreviewItem::heightChanged, this, &PreviewItem::syncSize);
0038     connect(this, &PreviewItem::bridgeChanged, this, &PreviewItem::createDecoration);
0039     connect(this, &PreviewItem::settingsChanged, this, &PreviewItem::createDecoration);
0040 }
0041 
0042 PreviewItem::~PreviewItem()
0043 {
0044     m_decoration->deleteLater();
0045     if (m_bridge) {
0046         m_bridge->unregisterPreviewItem(this);
0047     }
0048 }
0049 
0050 void PreviewItem::componentComplete()
0051 {
0052     QQuickPaintedItem::componentComplete();
0053     createDecoration();
0054     if (m_decoration) {
0055         m_decoration->setSettings(m_settings->settings());
0056         m_decoration->init();
0057         syncSize();
0058     }
0059 }
0060 
0061 void PreviewItem::createDecoration()
0062 {
0063     if (m_bridge.isNull() || m_settings.isNull() || m_decoration) {
0064         return;
0065     }
0066     Decoration *decoration = m_bridge->createDecoration(nullptr);
0067     m_client = m_bridge->lastCreatedClient();
0068     setDecoration(decoration);
0069 }
0070 
0071 Decoration *PreviewItem::decoration() const
0072 {
0073     return m_decoration;
0074 }
0075 
0076 void PreviewItem::setDecoration(Decoration *deco)
0077 {
0078     if (!deco || m_decoration == deco) {
0079         return;
0080     }
0081 
0082     m_decoration = deco;
0083     m_decoration->setProperty("visualParent", QVariant::fromValue(this));
0084     connect(m_decoration, &Decoration::bordersChanged, this, &PreviewItem::syncSize);
0085     connect(m_decoration, &Decoration::shadowChanged, this, &PreviewItem::syncSize);
0086     connect(m_decoration, &Decoration::shadowChanged, this, &PreviewItem::shadowChanged);
0087     connect(m_decoration, &Decoration::damaged, this, [this]() {
0088         update();
0089     });
0090     Q_EMIT decorationChanged(m_decoration);
0091 }
0092 
0093 QColor PreviewItem::windowColor() const
0094 {
0095     return m_windowColor;
0096 }
0097 
0098 void PreviewItem::setWindowColor(const QColor &color)
0099 {
0100     if (m_windowColor == color) {
0101         return;
0102     }
0103     m_windowColor = color;
0104     Q_EMIT windowColorChanged(m_windowColor);
0105     update();
0106 }
0107 
0108 void PreviewItem::paint(QPainter *painter)
0109 {
0110     if (!m_decoration) {
0111         return;
0112     }
0113     int paddingLeft = 0;
0114     int paddingTop = 0;
0115     int paddingRight = 0;
0116     int paddingBottom = 0;
0117     paintShadow(painter, paddingLeft, paddingRight, paddingTop, paddingBottom);
0118     m_decoration->paint(painter, QRect(0, 0, width(), height()));
0119     if (m_drawBackground) {
0120         painter->fillRect(m_decoration->borderLeft(), m_decoration->borderTop(),
0121                           width() - m_decoration->borderLeft() - m_decoration->borderRight() - paddingLeft - paddingRight,
0122                           height() - m_decoration->borderTop() - m_decoration->borderBottom() - paddingTop - paddingBottom,
0123                           m_windowColor);
0124     }
0125 }
0126 
0127 void PreviewItem::paintShadow(QPainter *painter, int &paddingLeft, int &paddingRight, int &paddingTop, int &paddingBottom)
0128 {
0129     const auto &shadow = m_decoration->shadow();
0130     if (!shadow) {
0131         return;
0132     }
0133 
0134     paddingLeft = shadow->paddingLeft();
0135     paddingTop = shadow->paddingTop();
0136     paddingRight = shadow->paddingRight();
0137     paddingBottom = shadow->paddingBottom();
0138 
0139     const QImage shadowPixmap = shadow->shadow();
0140     if (shadowPixmap.isNull()) {
0141         return;
0142     }
0143 
0144     const QRect outerRect(-paddingLeft, -paddingTop, width(), height());
0145     const QRect shadowRect(shadowPixmap.rect());
0146 
0147     const QSize topLeftSize(shadow->topLeftGeometry().size());
0148     QRect topLeftTarget(
0149         QPoint(outerRect.x(), outerRect.y()),
0150         topLeftSize);
0151 
0152     const QSize topRightSize(shadow->topRightGeometry().size());
0153     QRect topRightTarget(
0154         QPoint(outerRect.x() + outerRect.width() - topRightSize.width(),
0155                outerRect.y()),
0156         topRightSize);
0157 
0158     const QSize bottomRightSize(shadow->bottomRightGeometry().size());
0159     QRect bottomRightTarget(
0160         QPoint(outerRect.x() + outerRect.width() - bottomRightSize.width(),
0161                outerRect.y() + outerRect.height() - bottomRightSize.height()),
0162         bottomRightSize);
0163 
0164     const QSize bottomLeftSize(shadow->bottomLeftGeometry().size());
0165     QRect bottomLeftTarget(
0166         QPoint(outerRect.x(),
0167                outerRect.y() + outerRect.height() - bottomLeftSize.height()),
0168         bottomLeftSize);
0169 
0170     // Re-distribute the corner tiles so no one of them is overlapping with others.
0171     // By doing this, we assume that shadow's corner tiles are symmetric
0172     // and it is OK to not draw top/right/bottom/left tile between corners.
0173     // For example, let's say top-left and top-right tiles are overlapping.
0174     // In that case, the right side of the top-left tile will be shifted to left,
0175     // the left side of the top-right tile will shifted to right, and the top
0176     // tile won't be rendered.
0177     bool drawTop = true;
0178     if (topLeftTarget.x() + topLeftTarget.width() >= topRightTarget.x()) {
0179         const float halfOverlap = std::abs(topLeftTarget.x() + topLeftTarget.width() - topRightTarget.x()) / 2.0f;
0180         topLeftTarget.setRight(topLeftTarget.right() - std::floor(halfOverlap));
0181         topRightTarget.setLeft(topRightTarget.left() + std::ceil(halfOverlap));
0182         drawTop = false;
0183     }
0184 
0185     bool drawRight = true;
0186     if (topRightTarget.y() + topRightTarget.height() >= bottomRightTarget.y()) {
0187         const float halfOverlap = std::abs(topRightTarget.y() + topRightTarget.height() - bottomRightTarget.y()) / 2.0f;
0188         topRightTarget.setBottom(topRightTarget.bottom() - std::floor(halfOverlap));
0189         bottomRightTarget.setTop(bottomRightTarget.top() + std::ceil(halfOverlap));
0190         drawRight = false;
0191     }
0192 
0193     bool drawBottom = true;
0194     if (bottomLeftTarget.x() + bottomLeftTarget.width() >= bottomRightTarget.x()) {
0195         const float halfOverlap = std::abs(bottomLeftTarget.x() + bottomLeftTarget.width() - bottomRightTarget.x()) / 2.0f;
0196         bottomLeftTarget.setRight(bottomLeftTarget.right() - std::floor(halfOverlap));
0197         bottomRightTarget.setLeft(bottomRightTarget.left() + std::ceil(halfOverlap));
0198         drawBottom = false;
0199     }
0200 
0201     bool drawLeft = true;
0202     if (topLeftTarget.y() + topLeftTarget.height() >= bottomLeftTarget.y()) {
0203         const float halfOverlap = std::abs(topLeftTarget.y() + topLeftTarget.height() - bottomLeftTarget.y()) / 2.0f;
0204         topLeftTarget.setBottom(topLeftTarget.bottom() - std::floor(halfOverlap));
0205         bottomLeftTarget.setTop(bottomLeftTarget.top() + std::ceil(halfOverlap));
0206         drawLeft = false;
0207     }
0208 
0209     painter->translate(paddingLeft, paddingTop);
0210 
0211     painter->drawImage(topLeftTarget, shadowPixmap,
0212                        QRect(QPoint(0, 0), topLeftTarget.size()));
0213 
0214     painter->drawImage(topRightTarget, shadowPixmap,
0215                        QRect(QPoint(shadowRect.width() - topRightTarget.width(), 0),
0216                              topRightTarget.size()));
0217 
0218     painter->drawImage(bottomRightTarget, shadowPixmap,
0219                        QRect(QPoint(shadowRect.width() - bottomRightTarget.width(),
0220                                     shadowRect.height() - bottomRightTarget.height()),
0221                              bottomRightTarget.size()));
0222 
0223     painter->drawImage(bottomLeftTarget, shadowPixmap,
0224                        QRect(QPoint(0, shadowRect.height() - bottomLeftTarget.height()),
0225                              bottomLeftTarget.size()));
0226 
0227     if (drawTop) {
0228         QRect topTarget(topLeftTarget.x() + topLeftTarget.width(),
0229                         topLeftTarget.y(),
0230                         topRightTarget.x() - topLeftTarget.x() - topLeftTarget.width(),
0231                         topRightTarget.height());
0232         QRect topSource(shadow->topGeometry());
0233         topSource.setHeight(topTarget.height());
0234         topSource.moveTop(shadowRect.top());
0235         painter->drawImage(topTarget, shadowPixmap, topSource);
0236     }
0237 
0238     if (drawRight) {
0239         QRect rightTarget(topRightTarget.x(),
0240                           topRightTarget.y() + topRightTarget.height(),
0241                           topRightTarget.width(),
0242                           bottomRightTarget.y() - topRightTarget.y() - topRightTarget.height());
0243         QRect rightSource(shadow->rightGeometry());
0244         rightSource.setWidth(rightTarget.width());
0245         rightSource.moveRight(shadowRect.right());
0246         painter->drawImage(rightTarget, shadowPixmap, rightSource);
0247     }
0248 
0249     if (drawBottom) {
0250         QRect bottomTarget(bottomLeftTarget.x() + bottomLeftTarget.width(),
0251                            bottomLeftTarget.y(),
0252                            bottomRightTarget.x() - bottomLeftTarget.x() - bottomLeftTarget.width(),
0253                            bottomRightTarget.height());
0254         QRect bottomSource(shadow->bottomGeometry());
0255         bottomSource.setHeight(bottomTarget.height());
0256         bottomSource.moveBottom(shadowRect.bottom());
0257         painter->drawImage(bottomTarget, shadowPixmap, bottomSource);
0258     }
0259 
0260     if (drawLeft) {
0261         QRect leftTarget(topLeftTarget.x(),
0262                          topLeftTarget.y() + topLeftTarget.height(),
0263                          topLeftTarget.width(),
0264                          bottomLeftTarget.y() - topLeftTarget.y() - topLeftTarget.height());
0265         QRect leftSource(shadow->leftGeometry());
0266         leftSource.setWidth(leftTarget.width());
0267         leftSource.moveLeft(shadowRect.left());
0268         painter->drawImage(leftTarget, shadowPixmap, leftSource);
0269     }
0270 }
0271 
0272 static QMouseEvent cloneEventWithPadding(QMouseEvent *event, int paddingLeft, int paddingTop)
0273 {
0274     return QMouseEvent(
0275         event->type(),
0276         event->localPos() - QPointF(paddingLeft, paddingTop),
0277         event->button(),
0278         event->buttons(),
0279         event->modifiers());
0280 }
0281 
0282 static QHoverEvent cloneEventWithPadding(QHoverEvent *event, int paddingLeft, int paddingTop)
0283 {
0284     return QHoverEvent(
0285         event->type(),
0286         event->posF() - QPointF(paddingLeft, paddingTop),
0287         event->oldPosF() - QPointF(paddingLeft, paddingTop),
0288         event->modifiers());
0289 }
0290 
0291 template <typename E>
0292 void PreviewItem::proxyPassEvent(E *event) const
0293 {
0294     const auto &shadow = m_decoration->shadow();
0295     if (shadow) {
0296         E e = cloneEventWithPadding(event, shadow->paddingLeft(), shadow->paddingTop());
0297         QCoreApplication::instance()->sendEvent(decoration(), &e);
0298     } else {
0299         QCoreApplication::instance()->sendEvent(decoration(), event);
0300     }
0301     // Propagate events to parent
0302     event->ignore();
0303 }
0304 
0305 void PreviewItem::mouseDoubleClickEvent(QMouseEvent *event)
0306 {
0307     proxyPassEvent(event);
0308 }
0309 
0310 void PreviewItem::mousePressEvent(QMouseEvent *event)
0311 {
0312     proxyPassEvent(event);
0313 }
0314 
0315 void PreviewItem::mouseReleaseEvent(QMouseEvent *event)
0316 {
0317     proxyPassEvent(event);
0318 }
0319 
0320 void PreviewItem::mouseMoveEvent(QMouseEvent *event)
0321 {
0322     proxyPassEvent(event);
0323 }
0324 
0325 void PreviewItem::hoverEnterEvent(QHoverEvent *event)
0326 {
0327     proxyPassEvent(event);
0328 }
0329 
0330 void PreviewItem::hoverLeaveEvent(QHoverEvent *event)
0331 {
0332     proxyPassEvent(event);
0333 }
0334 
0335 void PreviewItem::hoverMoveEvent(QHoverEvent *event)
0336 {
0337     proxyPassEvent(event);
0338 }
0339 
0340 bool PreviewItem::isDrawingBackground() const
0341 {
0342     return m_drawBackground;
0343 }
0344 
0345 void PreviewItem::setDrawingBackground(bool set)
0346 {
0347     if (m_drawBackground == set) {
0348         return;
0349     }
0350     m_drawBackground = set;
0351     Q_EMIT drawingBackgroundChanged(set);
0352 }
0353 
0354 PreviewBridge *PreviewItem::bridge() const
0355 {
0356     return m_bridge.data();
0357 }
0358 
0359 void PreviewItem::setBridge(PreviewBridge *bridge)
0360 {
0361     if (m_bridge == bridge) {
0362         return;
0363     }
0364     if (m_bridge) {
0365         m_bridge->unregisterPreviewItem(this);
0366     }
0367     m_bridge = bridge;
0368     if (m_bridge) {
0369         m_bridge->registerPreviewItem(this);
0370     }
0371     Q_EMIT bridgeChanged();
0372 }
0373 
0374 Settings *PreviewItem::settings() const
0375 {
0376     return m_settings.data();
0377 }
0378 
0379 void PreviewItem::setSettings(Settings *settings)
0380 {
0381     if (m_settings == settings) {
0382         return;
0383     }
0384     m_settings = settings;
0385     Q_EMIT settingsChanged();
0386 }
0387 
0388 PreviewClient *PreviewItem::client()
0389 {
0390     return m_client.data();
0391 }
0392 
0393 void PreviewItem::syncSize()
0394 {
0395     if (!m_client) {
0396         return;
0397     }
0398     int widthOffset = 0;
0399     int heightOffset = 0;
0400     auto shadow = m_decoration->shadow();
0401     if (shadow) {
0402         widthOffset = shadow->paddingLeft() + shadow->paddingRight();
0403         heightOffset = shadow->paddingTop() + shadow->paddingBottom();
0404     }
0405     m_client->setWidth(width() - m_decoration->borderLeft() - m_decoration->borderRight() - widthOffset);
0406     m_client->setHeight(height() - m_decoration->borderTop() - m_decoration->borderBottom() - heightOffset);
0407 }
0408 
0409 DecorationShadow *PreviewItem::shadow() const
0410 {
0411     if (!m_decoration) {
0412         return nullptr;
0413     }
0414     return m_decoration->shadow().get();
0415 }
0416 
0417 }
0418 }
0419 
0420 #include "moc_previewitem.cpp"