File indexing completed on 2024-05-05 05:35:25

0001 //////////////////////////////////////////////////////////////////////////////
0002 // oxygenframeshadow.h
0003 // handle sunken frames' shadows
0004 // -------------------
0005 //
0006 // SPDX-FileCopyrightText: 2010 Hugo Pereira Da Costa <hugo.pereira@free.fr>
0007 //
0008 // Largely inspired from skulpture widget style
0009 // SPDX-FileCopyrightText: 2007-2009 Christoph Feck <christoph@maxiom.de>
0010 //
0011 // SPDX-License-Identifier: MIT
0012 //////////////////////////////////////////////////////////////////////////////
0013 
0014 #include "oxygenframeshadow.h"
0015 
0016 #include <QAbstractScrollArea>
0017 #include <QApplication>
0018 #include <QDebug>
0019 #include <QFrame>
0020 #include <QMouseEvent>
0021 #include <QPainter>
0022 #include <QSplitter>
0023 
0024 #include <KColorUtils>
0025 
0026 namespace Oxygen
0027 {
0028 //____________________________________________________________________________________
0029 bool FrameShadowFactory::registerWidget(QWidget *widget, StyleHelper &helper)
0030 {
0031     if (!widget)
0032         return false;
0033     if (isRegistered(widget))
0034         return false;
0035 
0036     // check whether widget is a frame, and has the proper shape
0037     bool accepted = false;
0038     bool flat = false;
0039 
0040     // cast to frame and check
0041     QFrame *frame(qobject_cast<QFrame *>(widget));
0042     if (frame) {
0043         // also do not install on QSplitter
0044         /*
0045         due to Qt, splitters are set with a frame style that matches the condition below,
0046         though no shadow should be installed, obviously
0047         */
0048         if (qobject_cast<QSplitter *>(widget))
0049             return false;
0050 
0051         // further checks on frame shape, and parent
0052         if (frame->frameStyle() == (QFrame::StyledPanel | QFrame::Sunken))
0053             accepted = true;
0054         else if (widget->parent() && widget->parent()->inherits("QComboBoxPrivateContainer")) {
0055             accepted = true;
0056             flat = true;
0057         }
0058 
0059     } else if (widget->inherits("KTextEditor::View"))
0060         accepted = true;
0061 
0062     if (!accepted)
0063         return false;
0064 
0065     // make sure that the widget is not embedded into a KHTMLView
0066     QWidget *parent(widget->parentWidget());
0067     while (parent && !parent->isTopLevel()) {
0068         if (parent->inherits("KHTMLView"))
0069             return false;
0070         parent = parent->parentWidget();
0071     }
0072 
0073     // store in set
0074     _registeredWidgets.insert(widget);
0075 
0076     // catch object destruction
0077     connect(widget, SIGNAL(destroyed(QObject *)), SLOT(widgetDestroyed(QObject *)));
0078 
0079     // install shadow
0080     installShadows(widget, helper, flat);
0081 
0082     return true;
0083 }
0084 
0085 //____________________________________________________________________________________
0086 void FrameShadowFactory::unregisterWidget(QWidget *widget)
0087 {
0088     if (!isRegistered(widget))
0089         return;
0090     _registeredWidgets.remove(widget);
0091     removeShadows(widget);
0092 }
0093 
0094 //____________________________________________________________________________________
0095 bool FrameShadowFactory::eventFilter(QObject *object, QEvent *event)
0096 {
0097     switch (event->type()) {
0098     // TODO: possibly implement ZOrderChange event, to make sure that
0099     // the shadow is always painted on top
0100     case QEvent::ZOrderChange: {
0101         raiseShadows(object);
0102         break;
0103     }
0104 
0105     case QEvent::Show:
0106         updateShadowsGeometry(object);
0107         update(object);
0108         break;
0109 
0110     case QEvent::Resize:
0111         updateShadowsGeometry(object);
0112         break;
0113 
0114     default:
0115         break;
0116     }
0117 
0118     return QObject::eventFilter(object, event);
0119 }
0120 
0121 //____________________________________________________________________________________
0122 void FrameShadowFactory::installShadows(QWidget *widget, StyleHelper &helper, bool flat)
0123 {
0124     removeShadows(widget);
0125 
0126     widget->installEventFilter(this);
0127 
0128     widget->installEventFilter(&_addEventFilter);
0129     if (!flat) {
0130         installShadow(widget, helper, ShadowAreaLeft);
0131         installShadow(widget, helper, ShadowAreaRight);
0132     }
0133 
0134     installShadow(widget, helper, ShadowAreaTop, flat);
0135     installShadow(widget, helper, ShadowAreaBottom, flat);
0136     widget->removeEventFilter(&_addEventFilter);
0137 }
0138 
0139 //____________________________________________________________________________________
0140 void FrameShadowFactory::removeShadows(QWidget *widget)
0141 {
0142     widget->removeEventFilter(this);
0143 
0144     const QList<QObject *> children = widget->children();
0145     for (QObject *child : children) {
0146         if (FrameShadowBase *shadow = qobject_cast<FrameShadowBase *>(child)) {
0147             shadow->hide();
0148             shadow->setParent(nullptr);
0149             shadow->deleteLater();
0150         }
0151     }
0152 }
0153 
0154 //____________________________________________________________________________________
0155 void FrameShadowFactory::updateShadowsGeometry(QObject *object) const
0156 {
0157     const QList<QObject *> children = object->children();
0158     for (QObject *child : children) {
0159         if (FrameShadowBase *shadow = qobject_cast<FrameShadowBase *>(child)) {
0160             shadow->updateGeometry();
0161         }
0162     }
0163 }
0164 
0165 //____________________________________________________________________________________
0166 void FrameShadowFactory::updateShadowsGeometry(const QObject *object, QRect rect) const
0167 {
0168     const QList<QObject *> children = object->children();
0169     for (QObject *child : children) {
0170         if (FrameShadowBase *shadow = qobject_cast<FrameShadowBase *>(child)) {
0171             shadow->updateGeometry(rect);
0172         }
0173     }
0174 }
0175 
0176 //____________________________________________________________________________________
0177 void FrameShadowFactory::raiseShadows(QObject *object) const
0178 {
0179     const QList<QObject *> children = object->children();
0180     for (QObject *child : children) {
0181         if (FrameShadowBase *shadow = qobject_cast<FrameShadowBase *>(child)) {
0182             shadow->raise();
0183         }
0184     }
0185 }
0186 
0187 //____________________________________________________________________________________
0188 void FrameShadowFactory::update(QObject *object) const
0189 {
0190     const QList<QObject *> children = object->children();
0191     for (QObject *child : children) {
0192         if (FrameShadowBase *shadow = qobject_cast<FrameShadowBase *>(child)) {
0193             shadow->update();
0194         }
0195     }
0196 }
0197 
0198 //____________________________________________________________________________________
0199 void FrameShadowFactory::setHasContrast(const QWidget *widget, bool value) const
0200 {
0201     const QList<QObject *> children = widget->children();
0202     for (QObject *child : children) {
0203         if (FrameShadowBase *shadow = qobject_cast<FrameShadowBase *>(child)) {
0204             shadow->setHasContrast(value);
0205         }
0206     }
0207 }
0208 
0209 //____________________________________________________________________________________
0210 void FrameShadowFactory::updateState(const QWidget *widget, bool focus, bool hover, qreal opacity, AnimationMode mode) const
0211 {
0212     const QList<QObject *> children = widget->children();
0213     for (QObject *child : children) {
0214         if (FrameShadowBase *shadow = qobject_cast<FrameShadowBase *>(child)) {
0215             shadow->updateState(focus, hover, opacity, mode);
0216         }
0217     }
0218 }
0219 
0220 //____________________________________________________________________________________
0221 void FrameShadowFactory::installShadow(QWidget *widget, StyleHelper &helper, ShadowArea area, bool flat) const
0222 {
0223     FrameShadowBase *shadow(nullptr);
0224     if (flat)
0225         shadow = new FlatFrameShadow(area, helper);
0226     else
0227         shadow = new SunkenFrameShadow(area, helper);
0228     shadow->setParent(widget);
0229     shadow->hide();
0230 }
0231 
0232 //____________________________________________________________________________________
0233 void FrameShadowFactory::widgetDestroyed(QObject *object)
0234 {
0235     _registeredWidgets.remove(object);
0236 }
0237 
0238 //____________________________________________________________________________________
0239 void FrameShadowBase::init()
0240 {
0241     setAttribute(Qt::WA_OpaquePaintEvent, false);
0242 
0243     setFocusPolicy(Qt::NoFocus);
0244     setAttribute(Qt::WA_TransparentForMouseEvents, true);
0245     setContextMenuPolicy(Qt::NoContextMenu);
0246 
0247     // grab viewport widget
0248     QWidget *viewport(FrameShadowBase::viewport());
0249     if (!viewport && parentWidget()) {
0250         viewport = parentWidget();
0251     }
0252 
0253     // set cursor from viewport
0254     if (viewport)
0255         setCursor(viewport->cursor());
0256 }
0257 
0258 //____________________________________________________________________________________
0259 QWidget *FrameShadowBase::viewport(void) const
0260 {
0261     if (!parentWidget())
0262         return nullptr;
0263     else if (QAbstractScrollArea *widget = qobject_cast<QAbstractScrollArea *>(parentWidget())) {
0264         return widget->viewport();
0265 
0266     } else
0267         return nullptr;
0268 }
0269 
0270 //____________________________________________________________________________________
0271 void SunkenFrameShadow::updateGeometry(QRect rect)
0272 {
0273     // show on first call
0274     if (isHidden())
0275         show();
0276 
0277     // store offsets between passed rect and parent widget rect
0278     QRect parentRect(parentWidget()->contentsRect());
0279     setMargins(
0280         QMargins(rect.left() - parentRect.left(), rect.top() - parentRect.top(), rect.right() - parentRect.right(), rect.bottom() - parentRect.bottom()));
0281 
0282     // adjust geometry to take out part that is not rendered anyway
0283     rect.adjust(1, 1, -1, -1);
0284 
0285     // adjust geometry
0286     const int shadowSize(3);
0287     switch (shadowArea()) {
0288     case ShadowAreaTop:
0289         rect.setHeight(shadowSize);
0290         break;
0291 
0292     case ShadowAreaBottom:
0293         rect.setTop(rect.bottom() - shadowSize + 1);
0294         break;
0295 
0296     case ShadowAreaLeft:
0297         rect.setWidth(shadowSize);
0298         rect.adjust(0, shadowSize, 0, -shadowSize);
0299         break;
0300 
0301     case ShadowAreaRight:
0302         rect.setLeft(rect.right() - shadowSize + 1);
0303         rect.adjust(0, shadowSize, 0, -shadowSize);
0304         break;
0305 
0306     default:
0307         return;
0308     }
0309 
0310     setGeometry(rect);
0311 }
0312 
0313 //____________________________________________________________________________________
0314 void SunkenFrameShadow::updateState(bool focus, bool hover, qreal opacity, AnimationMode mode)
0315 {
0316     bool changed(false);
0317     if (_hasFocus != focus) {
0318         _hasFocus = focus;
0319         changed |= true;
0320     }
0321     if (_mouseOver != hover) {
0322         _mouseOver = hover;
0323         changed |= !_hasFocus;
0324     }
0325     if (_mode != mode) {
0326         _mode = mode;
0327         changed |= (_mode == AnimationNone) || (_mode == AnimationFocus) || (_mode == AnimationHover && !_hasFocus);
0328     }
0329 
0330     if (_opacity != opacity) {
0331         _opacity = opacity;
0332         changed |= (_mode != AnimationNone);
0333     }
0334     if (changed) {
0335         if (QWidget *viewport = this->viewport()) {
0336             // need to disable viewport updates to avoid some redundant painting
0337             // besides it fixes one visual glitch (from Qt) in QTableViews
0338             viewport->setUpdatesEnabled(false);
0339             update();
0340             viewport->setUpdatesEnabled(true);
0341 
0342         } else
0343             update();
0344     }
0345 }
0346 
0347 //____________________________________________________________________________________
0348 void SunkenFrameShadow::paintEvent(QPaintEvent *event)
0349 {
0350     // this fixes shadows in frames that change frameStyle() after polish()
0351     if (QFrame *frame = qobject_cast<QFrame *>(parentWidget())) {
0352         if (frame->frameStyle() != (QFrame::StyledPanel | QFrame::Sunken))
0353             return;
0354     }
0355 
0356     const QRect parentRect(parentWidget()->contentsRect().translated(mapFromParent(QPoint(0, 0))));
0357     const QRect rect(parentRect.adjusted(margins().left(), margins().top(), margins().right(), margins().bottom()));
0358 
0359     // render
0360     QPainter painter(this);
0361     painter.setClipRegion(event->region());
0362 
0363     StyleOptions options(HoleOutline);
0364     if (_hasFocus)
0365         options |= Focus;
0366     if (_mouseOver)
0367         options |= Hover;
0368     if (hasContrast())
0369         options |= HoleContrast;
0370     _helper.renderHole(&painter, palette().color(QPalette::Window), rect, options, _opacity, _mode, TileSet::Ring);
0371 
0372     return;
0373 }
0374 
0375 //____________________________________________________________________________________
0376 void FlatFrameShadow::updateGeometry()
0377 {
0378     if (QWidget *widget = parentWidget())
0379         updateGeometry(widget->contentsRect());
0380 }
0381 
0382 //____________________________________________________________________________________
0383 void FlatFrameShadow::updateGeometry(QRect rect)
0384 {
0385     // show on first call
0386     if (isHidden())
0387         show();
0388 
0389     // store offsets between passed rect and parent widget rect
0390     QRect parentRect(parentWidget()->contentsRect());
0391     setMargins(
0392         QMargins(rect.left() - parentRect.left(), rect.top() - parentRect.top(), rect.right() - parentRect.right(), rect.bottom() - parentRect.bottom()));
0393 
0394     const int shadowSize(3);
0395     switch (shadowArea()) {
0396     case ShadowAreaTop:
0397         rect.setHeight(shadowSize);
0398         break;
0399 
0400     case ShadowAreaBottom:
0401         rect.setTop(rect.bottom() - shadowSize + 1);
0402         break;
0403 
0404     default:
0405         return;
0406     }
0407 
0408     setGeometry(rect);
0409 }
0410 
0411 //____________________________________________________________________________________
0412 void FlatFrameShadow::paintEvent(QPaintEvent *event)
0413 {
0414     // this fixes shadows in frames that change frameStyle() after polish()
0415     if (QFrame *frame = qobject_cast<QFrame *>(parentWidget())) {
0416         if (frame->frameStyle() != QFrame::NoFrame)
0417             return;
0418     }
0419 
0420     const QWidget *parent(parentWidget());
0421     const QRect parentRect(parent->contentsRect());
0422     const QRect rect(parentRect.adjusted(margins().left(), margins().top(), margins().right(), margins().bottom()));
0423 
0424     QPixmap pixmap(_helper.highDpiPixmap(size()));
0425     {
0426         pixmap.fill(Qt::transparent);
0427         QPainter painter(&pixmap);
0428         painter.setClipRegion(event->region());
0429         painter.setRenderHints(QPainter::Antialiasing);
0430         painter.translate(-geometry().topLeft());
0431         painter.setCompositionMode(QPainter::CompositionMode_DestinationOver);
0432         painter.setPen(Qt::NoPen);
0433         _helper.renderMenuBackground(&painter, geometry(), parent, parent->palette());
0434 
0435         // mask
0436         painter.setCompositionMode(QPainter::CompositionMode_DestinationOut);
0437         painter.setBrush(Qt::black);
0438         painter.drawRoundedRect(QRectF(rect), 2.5, 2.5);
0439     }
0440 
0441     QPainter painter(this);
0442     painter.setClipRegion(event->region());
0443     painter.fillRect(rect, Qt::transparent);
0444     painter.drawPixmap(QPoint(0, 0), pixmap);
0445 
0446     return;
0447 }
0448 }