File indexing completed on 2024-04-28 05:26:20

0001 /*
0002  * SPDX-FileCopyrightText: 2014 Hugo Pereira Da Costa <hugo.pereira@free.fr>
0003  *
0004  * SPDX-License-Identifier: GPL-2.0-or-later
0005  */
0006 
0007 #include "breezeframeshadow.h"
0008 
0009 #include "breezemetrics.h"
0010 
0011 #include <QAbstractScrollArea>
0012 #include <QApplication>
0013 #include <QDebug>
0014 #include <QFrame>
0015 #include <QMouseEvent>
0016 #include <QPainter>
0017 #include <QSplitter>
0018 
0019 #include <KColorUtils>
0020 
0021 namespace Breeze
0022 {
0023 //____________________________________________________________________________________
0024 bool FrameShadowFactory::registerWidget(QWidget *widget, Helper &helper)
0025 {
0026     if (!widget) {
0027         return false;
0028     }
0029     if (isRegistered(widget)) {
0030         return false;
0031     }
0032 
0033     // check whether widget is a frame, and has the proper shape
0034     bool accepted = false;
0035 
0036     // cast to frame and check
0037     QFrame *frame(qobject_cast<QFrame *>(widget));
0038     if (frame) {
0039         // also do not install on QSplitter
0040         /*
0041         due to Qt, splitters are set with a frame style that matches the condition below,
0042         though no shadow should be installed, obviously
0043         */
0044         if (qobject_cast<QSplitter *>(widget)) {
0045             return false;
0046         }
0047 
0048         // further checks on frame shape, and parent
0049         if (frame->frameStyle() == (QFrame::StyledPanel | QFrame::Sunken)) {
0050             accepted = true;
0051         }
0052 
0053     } else if (widget->inherits("KTextEditor::View")) {
0054         accepted = true;
0055     }
0056 
0057     if (!accepted) {
0058         return false;
0059     }
0060 
0061     // make sure that the widget is not embedded into a KHTMLView
0062     QWidget *parent(widget->parentWidget());
0063     while (parent && !parent->isWindow()) {
0064         if (parent->inherits("KHTMLView")) {
0065             return false;
0066         }
0067         parent = parent->parentWidget();
0068     }
0069 
0070     // store in set
0071     _registeredWidgets.insert(widget);
0072 
0073     // catch object destruction
0074     connect(widget, &QObject::destroyed, this, &FrameShadowFactory::widgetDestroyed);
0075 
0076     // install shadow
0077     installShadows(widget, helper);
0078 
0079     return true;
0080 }
0081 
0082 //____________________________________________________________________________________
0083 void FrameShadowFactory::unregisterWidget(QWidget *widget)
0084 {
0085     if (!isRegistered(widget)) {
0086         return;
0087     }
0088     _registeredWidgets.remove(widget);
0089     removeShadows(widget);
0090 }
0091 
0092 //____________________________________________________________________________________
0093 bool FrameShadowFactory::eventFilter(QObject *object, QEvent *event)
0094 {
0095     switch (event->type()) {
0096     // TODO: possibly implement ZOrderChange event, to make sure that
0097     // the shadow is always painted on top
0098     case QEvent::ZOrderChange: {
0099         raiseShadows(object);
0100         break;
0101     }
0102 
0103     default:
0104         break;
0105     }
0106 
0107     return QObject::eventFilter(object, event);
0108 }
0109 
0110 //____________________________________________________________________________________
0111 void FrameShadowFactory::installShadows(QWidget *widget, Helper &helper)
0112 {
0113     removeShadows(widget);
0114 
0115     widget->installEventFilter(this);
0116 
0117     widget->installEventFilter(&_addEventFilter);
0118     installShadow(widget, helper, SideTop);
0119     installShadow(widget, helper, SideBottom);
0120     widget->removeEventFilter(&_addEventFilter);
0121 }
0122 
0123 //____________________________________________________________________________________
0124 void FrameShadowFactory::removeShadows(QWidget *widget)
0125 {
0126     widget->removeEventFilter(this);
0127 
0128     const QList<QObject *> children = widget->children();
0129     for (QObject *child : children) {
0130         if (FrameShadow *shadow = qobject_cast<FrameShadow *>(child)) {
0131             shadow->hide();
0132             shadow->setParent(nullptr);
0133             shadow->deleteLater();
0134         }
0135     }
0136 }
0137 
0138 //____________________________________________________________________________________
0139 void FrameShadowFactory::updateShadowsGeometry(const QObject *object, QRect rect) const
0140 {
0141     const QList<QObject *> &children = object->children();
0142     for (QObject *child : children) {
0143         if (FrameShadow *shadow = qobject_cast<FrameShadow *>(child)) {
0144             shadow->updateGeometry(rect);
0145         }
0146     }
0147 }
0148 
0149 //____________________________________________________________________________________
0150 void FrameShadowFactory::raiseShadows(QObject *object) const
0151 {
0152     const QList<QObject *> &children = object->children();
0153     for (QObject *child : children) {
0154         if (FrameShadow *shadow = qobject_cast<FrameShadow *>(child)) {
0155             shadow->raise();
0156         }
0157     }
0158 }
0159 
0160 //____________________________________________________________________________________
0161 void FrameShadowFactory::update(QObject *object) const
0162 {
0163     const QList<QObject *> &children = object->children();
0164     for (QObject *child : children) {
0165         if (FrameShadow *shadow = qobject_cast<FrameShadow *>(child)) {
0166             shadow->update();
0167         }
0168     }
0169 }
0170 
0171 //____________________________________________________________________________________
0172 void FrameShadowFactory::updateState(const QWidget *widget, bool focus, bool hover, qreal opacity, AnimationMode mode) const
0173 {
0174     const QList<QObject *> &children = widget->children();
0175     for (QObject *child : children) {
0176         if (FrameShadow *shadow = qobject_cast<FrameShadow *>(child)) {
0177             shadow->updateState(focus, hover, opacity, mode);
0178         }
0179     }
0180 }
0181 
0182 //____________________________________________________________________________________
0183 void FrameShadowFactory::installShadow(QWidget *widget, Helper &helper, Side area) const
0184 {
0185     FrameShadow *shadow(nullptr);
0186     shadow = new FrameShadow(area, helper);
0187     shadow->setParent(widget);
0188     shadow->hide();
0189 }
0190 
0191 //____________________________________________________________________________________
0192 void FrameShadowFactory::widgetDestroyed(QObject *object)
0193 {
0194     _registeredWidgets.remove(object);
0195 }
0196 
0197 //____________________________________________________________________________________
0198 FrameShadow::FrameShadow(Side area, Helper &helper)
0199     : _helper(helper)
0200     , _area(area)
0201 {
0202     setAttribute(Qt::WA_OpaquePaintEvent, false);
0203 
0204     setFocusPolicy(Qt::NoFocus);
0205     setAttribute(Qt::WA_TransparentForMouseEvents, true);
0206     setContextMenuPolicy(Qt::NoContextMenu);
0207 
0208     // grab viewport widget
0209     QWidget *viewport(this->viewport());
0210 
0211     // set cursor from viewport
0212     if (viewport) {
0213         setCursor(viewport->cursor());
0214     }
0215 }
0216 
0217 //____________________________________________________________________________________
0218 void FrameShadow::updateGeometry(QRect rect)
0219 {
0220     // show on first call
0221     if (isHidden()) {
0222         show();
0223     }
0224 
0225     // store offsets between passed rect and parent widget rect
0226     QRect parentRect(parentWidget()->contentsRect());
0227     _margins = QMargins(rect.left() - parentRect.left(), rect.top() - parentRect.top(), rect.right() - parentRect.right(), rect.bottom() - parentRect.bottom());
0228 
0229     // for efficiency, take out the part for which nothing is rendered
0230     rect.adjust(1, 1, -1, -1);
0231 
0232     // adjust geometry
0233     const int shadowSize(Metrics::Frame_FrameRadius);
0234     switch (_area) {
0235     case SideTop:
0236         rect.setHeight(shadowSize);
0237         break;
0238 
0239     case SideBottom:
0240         rect.setTop(rect.bottom() - shadowSize + 1);
0241         break;
0242 
0243     case SideLeft:
0244         rect.setWidth(shadowSize);
0245         rect.adjust(0, shadowSize, 0, -shadowSize);
0246         break;
0247 
0248     case SideRight:
0249         rect.setLeft(rect.right() - shadowSize + 1);
0250         rect.adjust(0, shadowSize, 0, -shadowSize);
0251         break;
0252 
0253     default:
0254         return;
0255     }
0256 
0257     setGeometry(rect);
0258 }
0259 
0260 //____________________________________________________________________________________
0261 void FrameShadow::updateState(bool focus, bool hover, qreal opacity, AnimationMode mode)
0262 {
0263     bool changed(false);
0264     if (_hasFocus != focus) {
0265         _hasFocus = focus;
0266         changed |= true;
0267     }
0268     if (_mouseOver != hover) {
0269         _mouseOver = hover;
0270         changed |= !_hasFocus;
0271     }
0272     if (_mode != mode) {
0273         _mode = mode;
0274         changed |= (_mode == AnimationNone) || (_mode == AnimationFocus) || (_mode == AnimationHover && !_hasFocus);
0275     }
0276 
0277     if (_opacity != opacity) {
0278         _opacity = opacity;
0279         changed |= (_mode != AnimationNone);
0280     }
0281     if (changed) {
0282         if (QWidget *viewport = this->viewport()) {
0283             // need to disable viewport updates to avoid some redundant painting
0284             // besides it fixes one visual glitch (from Qt) in QTableViews
0285             viewport->setUpdatesEnabled(false);
0286             update();
0287             viewport->setUpdatesEnabled(true);
0288 
0289         } else {
0290             update();
0291         }
0292     }
0293 }
0294 
0295 //____________________________________________________________________________________
0296 void FrameShadow::paintEvent(QPaintEvent *event)
0297 {
0298     // this fixes shadows in frames that change frameStyle() after polish()
0299     if (QFrame *frame = qobject_cast<QFrame *>(parentWidget())) {
0300         if (frame->frameStyle() != (QFrame::StyledPanel | QFrame::Sunken)) {
0301             return;
0302         }
0303     }
0304 
0305     const QRect parentRect(parentWidget()->contentsRect().translated(mapFromParent(QPoint(0, 0))));
0306     const QRect rect(parentRect.adjusted(_margins.left(), _margins.top(), _margins.right(), _margins.bottom()));
0307 
0308     // render
0309     QPainter painter(this);
0310     painter.setClipRegion(event->region());
0311     painter.setRenderHint(QPainter::Antialiasing);
0312 
0313     const QColor outline(_helper.frameOutlineColor(palette(), _mouseOver, _hasFocus, _opacity, _mode));
0314     painter.setCompositionMode(QPainter::CompositionMode_SourceOver);
0315     _helper.renderFrame(&painter, rect, QColor(), outline);
0316 }
0317 
0318 //____________________________________________________________________________________
0319 QWidget *FrameShadow::viewport() const
0320 {
0321     if (!parentWidget()) {
0322         return nullptr;
0323     } else if (QAbstractScrollArea *widget = qobject_cast<QAbstractScrollArea *>(parentWidget())) {
0324         return widget->viewport();
0325 
0326     } else {
0327         return nullptr;
0328     }
0329 }
0330 
0331 }