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"