Warning, file /plasma/oxygen/kstyle/oxygenshadowhelper.cpp was not indexed or was modified since last indexation (in which case cross-reference links may be missing, inaccurate or erroneous).

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 }