File indexing completed on 2024-05-19 09:27:58
0001 ////////////////////////////////////////////////////////////////////////////// 0002 // oxygenshadowhelper.h 0003 // handle shadow pixmaps passed to window manager via X property 0004 // ------------------- 0005 // 0006 // SPDX-FileCopyrightText: 2010 Hugo Pereira Da Costa <hugo.pereira@free.fr> 0007 // SPDX-FileCopyrightText: 2020 Vlad Zahorodnii <vlad.zahorodnii@kde.org> 0008 // 0009 // SPDX-License-Identifier: MIT 0010 ////////////////////////////////////////////////////////////////////////////// 0011 0012 #include "oxygenshadowhelper.h" 0013 0014 #include "oxygenpropertynames.h" 0015 #include "oxygenshadowcache.h" 0016 #include "oxygenstylehelper.h" 0017 0018 #include <QApplication> 0019 #include <QDockWidget> 0020 #include <QEvent> 0021 #include <QMenu> 0022 #include <QPainter> 0023 #include <QTextStream> 0024 #include <QToolBar> 0025 0026 namespace Oxygen 0027 { 0028 0029 //_____________________________________________________ 0030 ShadowHelper::ShadowHelper(QObject *parent, StyleHelper &helper) 0031 : QObject(parent) 0032 , _helper(helper) 0033 , _shadowCache(new ShadowCache(helper)) 0034 , _size(0) 0035 { 0036 } 0037 0038 //_______________________________________________________ 0039 ShadowHelper::~ShadowHelper(void) 0040 { 0041 qDeleteAll(_shadows); 0042 delete _shadowCache; 0043 } 0044 0045 //______________________________________________ 0046 void ShadowHelper::reset(void) 0047 { 0048 _platformTiles.clear(); 0049 _platformDockTiles.clear(); 0050 0051 _tiles = TileSet(); 0052 _dockTiles = TileSet(); 0053 0054 // reset size 0055 _size = 0; 0056 } 0057 0058 //_______________________________________________________ 0059 bool ShadowHelper::registerWidget(QWidget *widget, bool force) 0060 { 0061 // make sure widget is not already registered 0062 if (_widgets.contains(widget)) 0063 return false; 0064 0065 // check if widget qualifies 0066 if (!(force || acceptWidget(widget))) { 0067 return false; 0068 } 0069 0070 // try create shadow directly 0071 installShadows(widget); 0072 _widgets.insert(widget); 0073 0074 // install event filter 0075 widget->removeEventFilter(this); 0076 widget->installEventFilter(this); 0077 0078 // connect destroy signal 0079 connect(widget, SIGNAL(destroyed(QObject *)), SLOT(widgetDeleted(QObject *))); 0080 0081 return true; 0082 } 0083 0084 //_______________________________________________________ 0085 void ShadowHelper::unregisterWidget(QWidget *widget) 0086 { 0087 if (_widgets.remove(widget)) { 0088 // uninstall the event filter 0089 widget->removeEventFilter(this); 0090 0091 // disconnect all signals 0092 disconnect(widget, nullptr, this, nullptr); 0093 0094 // uninstall the shadow 0095 uninstallShadows(widget); 0096 } 0097 } 0098 0099 //_______________________________________________________ 0100 void ShadowHelper::reparseCacheConfig(void) 0101 { 0102 // shadow cache 0103 _shadowCache->readConfig(); 0104 } 0105 0106 //_______________________________________________________ 0107 void ShadowHelper::loadConfig(void) 0108 { 0109 // reset 0110 reset(); 0111 0112 // retrieve shadow pixmap 0113 _size = _shadowCache->shadowSize(); 0114 0115 QPixmap pixmap(_shadowCache->pixmap(ShadowCache::Key())); 0116 const QSize pixmapSize(pixmap.size() / _helper.devicePixelRatio(pixmap)); 0117 if (!pixmap.isNull()) { 0118 QPainter painter(&pixmap); 0119 0120 // add transparency 0121 painter.setCompositionMode(QPainter::CompositionMode_DestinationIn); 0122 painter.fillRect(pixmap.rect(), QColor(0, 0, 0, 150)); 0123 } 0124 0125 // recreate tileset 0126 _tiles = TileSet(pixmap, pixmapSize.width() / 2, pixmapSize.height() / 2, 1, 1); 0127 0128 if (!pixmap.isNull()) { 0129 QPainter painter(&pixmap); 0130 0131 // add round corners 0132 const QRect cornerRect((pixmap.width() - 10) / 2, (pixmap.height() - 10) / 2, 10, 10); 0133 _helper.roundCorner(QPalette().color(QPalette::Window)).render(cornerRect, &painter); 0134 } 0135 0136 // recreate tileset 0137 _dockTiles = TileSet(pixmap, pixmapSize.width() / 2, pixmapSize.height() / 2, 1, 1); 0138 0139 // update property for registered widgets 0140 for (QWidget *widget : _widgets) { 0141 installShadows(widget); 0142 } 0143 } 0144 0145 //_______________________________________________________ 0146 bool ShadowHelper::eventFilter(QObject *object, QEvent *event) 0147 { 0148 // check event type 0149 if (event->type() != QEvent::WinIdChange) 0150 return false; 0151 0152 // cast widget 0153 QWidget *widget(static_cast<QWidget *>(object)); 0154 0155 // install shadows on the widget again 0156 installShadows(widget); 0157 0158 return false; 0159 } 0160 0161 //_______________________________________________________ 0162 void ShadowHelper::widgetDeleted(QObject *object) 0163 { 0164 QWidget *widget(static_cast<QWidget *>(object)); 0165 _widgets.remove(widget); 0166 } 0167 0168 //_______________________________________________________ 0169 void ShadowHelper::windowDeleted(QObject *object) 0170 { 0171 QWindow *window(static_cast<QWindow *>(object)); 0172 _shadows.remove(window); 0173 } 0174 0175 //_______________________________________________________ 0176 bool ShadowHelper::isMenu(QWidget *widget) const 0177 { 0178 return qobject_cast<QMenu *>(widget); 0179 } 0180 0181 //_______________________________________________________ 0182 bool ShadowHelper::isToolTip(QWidget *widget) const 0183 { 0184 return widget->inherits("QTipLabel") || (widget->windowFlags() & Qt::WindowType_Mask) == Qt::ToolTip; 0185 } 0186 0187 //_______________________________________________________ 0188 bool ShadowHelper::isDockWidget(QWidget *widget) const 0189 { 0190 return qobject_cast<QDockWidget *>(widget); 0191 } 0192 0193 //_______________________________________________________ 0194 bool ShadowHelper::isToolBar(QWidget *widget) const 0195 { 0196 return qobject_cast<QToolBar *>(widget) || widget->inherits("Q3ToolBar"); 0197 } 0198 0199 //_______________________________________________________ 0200 bool ShadowHelper::acceptWidget(QWidget *widget) const 0201 { 0202 // flags 0203 if (widget->property(PropertyNames::netWMSkipShadow).toBool()) 0204 return false; 0205 if (widget->property(PropertyNames::netWMForceShadow).toBool()) 0206 return true; 0207 0208 // menus 0209 if (isMenu(widget)) 0210 return true; 0211 0212 // combobox dropdown lists 0213 if (widget->inherits("QComboBoxPrivateContainer")) 0214 return true; 0215 0216 // tooltips 0217 if (isToolTip(widget) && !widget->inherits("Plasma::ToolTip")) { 0218 return true; 0219 } 0220 0221 // detached widgets 0222 if (isDockWidget(widget) || isToolBar(widget)) { 0223 return true; 0224 } 0225 0226 // reject 0227 return false; 0228 } 0229 0230 //______________________________________________ 0231 const QVector<KWindowShadowTile::Ptr> &ShadowHelper::createPlatformTiles(bool isDockWidget) 0232 { 0233 // make sure size is valid 0234 if (_size <= 0) 0235 return _platformTiles; 0236 0237 // make sure shadow tiles are not already initialized 0238 if (isDockWidget) { 0239 // make sure shadow tiles are not already initialized 0240 if (_platformDockTiles.isEmpty() && _dockTiles.isValid()) { 0241 _platformDockTiles.append(createPlatformTile(_dockTiles.pixmap(1))); 0242 _platformDockTiles.append(createPlatformTile(_dockTiles.pixmap(2))); 0243 _platformDockTiles.append(createPlatformTile(_dockTiles.pixmap(5))); 0244 _platformDockTiles.append(createPlatformTile(_dockTiles.pixmap(8))); 0245 _platformDockTiles.append(createPlatformTile(_dockTiles.pixmap(7))); 0246 _platformDockTiles.append(createPlatformTile(_dockTiles.pixmap(6))); 0247 _platformDockTiles.append(createPlatformTile(_dockTiles.pixmap(3))); 0248 _platformDockTiles.append(createPlatformTile(_dockTiles.pixmap(0))); 0249 } 0250 0251 } else if (_platformTiles.isEmpty() && _tiles.isValid()) { 0252 _platformTiles.append(createPlatformTile(_tiles.pixmap(1))); 0253 _platformTiles.append(createPlatformTile(_tiles.pixmap(2))); 0254 _platformTiles.append(createPlatformTile(_tiles.pixmap(5))); 0255 _platformTiles.append(createPlatformTile(_tiles.pixmap(8))); 0256 _platformTiles.append(createPlatformTile(_tiles.pixmap(7))); 0257 _platformTiles.append(createPlatformTile(_tiles.pixmap(6))); 0258 _platformTiles.append(createPlatformTile(_tiles.pixmap(3))); 0259 _platformTiles.append(createPlatformTile(_tiles.pixmap(0))); 0260 } 0261 0262 // return relevant list of shadow tiles 0263 return isDockWidget ? _platformDockTiles : _platformTiles; 0264 } 0265 0266 //______________________________________________ 0267 KWindowShadowTile::Ptr ShadowHelper::createPlatformTile(const QPixmap &pixmap) 0268 { 0269 KWindowShadowTile::Ptr tile = KWindowShadowTile::Ptr::create(); 0270 tile->setImage(pixmap.toImage()); 0271 return tile; 0272 } 0273 0274 //_______________________________________________________ 0275 void ShadowHelper::installShadows(QWidget *widget) 0276 { 0277 if (!widget) 0278 return; 0279 0280 // only toplevel widgets can cast drop-shadows 0281 if (!widget->isWindow()) 0282 return; 0283 0284 // widget must have valid native window 0285 if (!widget->testAttribute(Qt::WA_WState_Created)) 0286 return; 0287 0288 // create shadow tiles if needed 0289 const bool isDockWidget(this->isDockWidget(widget) || this->isToolBar(widget)); 0290 const QVector<KWindowShadowTile::Ptr> &tiles = createPlatformTiles(isDockWidget); 0291 if (tiles.count() != numTiles) 0292 return; 0293 0294 // get the underlying window for the widget 0295 QWindow *window = widget->windowHandle(); 0296 0297 // find a shadow associated with the widget 0298 KWindowShadow *&shadow = _shadows[window]; 0299 0300 if (!shadow) { 0301 // if there is no shadow yet, create one 0302 shadow = new KWindowShadow(window); 0303 0304 // connect destroy signal 0305 connect(window, &QWindow::destroyed, this, &ShadowHelper::windowDeleted); 0306 } 0307 0308 if (shadow->isCreated()) { 0309 shadow->destroy(); 0310 } 0311 0312 shadow->setTopTile(tiles[0]); 0313 shadow->setTopRightTile(tiles[1]); 0314 shadow->setRightTile(tiles[2]); 0315 shadow->setBottomRightTile(tiles[3]); 0316 shadow->setBottomTile(tiles[4]); 0317 shadow->setBottomLeftTile(tiles[5]); 0318 shadow->setLeftTile(tiles[6]); 0319 shadow->setTopLeftTile(tiles[7]); 0320 shadow->setWindow(window); 0321 shadow->setPadding(shadowMargins(widget)); 0322 shadow->create(); 0323 } 0324 0325 //_______________________________________________________ 0326 QMargins ShadowHelper::shadowMargins(QWidget *widget) const 0327 { 0328 // const qreal devicePixelRatio( _helper.devicePixelRatio( isDockWidget ? 0329 // _dockTiles.pixmap( 0 ):_tiles.pixmap( 0 ) ) ); 0330 const qreal devicePixelRatio(qApp->devicePixelRatio()); 0331 0332 // add padding 0333 /* 0334 in most cases all 4 paddings are identical, since offsets are handled when generating the pixmaps. 0335 There is one extra pixel needed with respect to actual shadow size, to deal with how 0336 menu backgrounds are rendered. 0337 Some special care is needed for QBalloonTip, since the later have an arrow 0338 */ 0339 0340 int topSize = 0; 0341 int rightSize = 0; 0342 int bottomSize = 0; 0343 int leftSize = 0; 0344 0345 if (isToolTip(widget) && widget->inherits("QBalloonTip")) { 0346 // balloon tip needs special margins to deal with the arrow 0347 const QMargins margins = widget->contentsMargins(); 0348 const int top = margins.top(); 0349 const int bottom = margins.bottom(); 0350 // also need to decrement default size further due to extra hard coded round corner 0351 const int size = (_size - 2) * devicePixelRatio; 0352 0353 // it seems arrow can be either to the top or the bottom. Adjust margins accordingly 0354 if (top > bottom) { 0355 topSize = size - (top - bottom); 0356 rightSize = size; 0357 bottomSize = size; 0358 leftSize = size; 0359 } else { 0360 topSize = size; 0361 rightSize = size; 0362 bottomSize = size - (bottom - top); 0363 leftSize = size; 0364 } 0365 0366 } else { 0367 const int size = _size * devicePixelRatio; 0368 topSize = size; 0369 rightSize = size; 0370 bottomSize = size; 0371 leftSize = size; 0372 } 0373 0374 return QMargins(leftSize, topSize, rightSize, bottomSize); 0375 } 0376 0377 //_______________________________________________________ 0378 void ShadowHelper::uninstallShadows(QWidget *widget) 0379 { 0380 delete _shadows.take(widget->windowHandle()); 0381 } 0382 }