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