/* * SPDX-FileCopyrightText: 2014 Hugo Pereira Da Costa * * SPDX-License-Identifier: GPL-2.0-or-later */ #include "breezeframeshadow.h" #include "breezemetrics.h" #include #include #include #include #include #include #include #include namespace Breeze { //____________________________________________________________________________________ bool FrameShadowFactory::registerWidget(QWidget *widget, Helper &helper) { if (!widget) { return false; } if (isRegistered(widget)) { return false; } // check whether widget is a frame, and has the proper shape bool accepted = false; // cast to frame and check QFrame *frame(qobject_cast(widget)); if (frame) { // also do not install on QSplitter /* due to Qt, splitters are set with a frame style that matches the condition below, though no shadow should be installed, obviously */ if (qobject_cast(widget)) { return false; } // further checks on frame shape, and parent if (frame->frameStyle() == (QFrame::StyledPanel | QFrame::Sunken)) { accepted = true; } } else if (widget->inherits("KTextEditor::View")) { accepted = true; } if (!accepted) { return false; } // make sure that the widget is not embedded into a KHTMLView QWidget *parent(widget->parentWidget()); while (parent && !parent->isWindow()) { if (parent->inherits("KHTMLView")) { return false; } parent = parent->parentWidget(); } // store in set _registeredWidgets.insert(widget); // catch object destruction connect(widget, &QObject::destroyed, this, &FrameShadowFactory::widgetDestroyed); // install shadow installShadows(widget, helper); return true; } //____________________________________________________________________________________ void FrameShadowFactory::unregisterWidget(QWidget *widget) { if (!isRegistered(widget)) { return; } _registeredWidgets.remove(widget); removeShadows(widget); } //____________________________________________________________________________________ bool FrameShadowFactory::eventFilter(QObject *object, QEvent *event) { switch (event->type()) { // TODO: possibly implement ZOrderChange event, to make sure that // the shadow is always painted on top case QEvent::ZOrderChange: { raiseShadows(object); break; } default: break; } return QObject::eventFilter(object, event); } //____________________________________________________________________________________ void FrameShadowFactory::installShadows(QWidget *widget, Helper &helper) { removeShadows(widget); widget->installEventFilter(this); widget->installEventFilter(&_addEventFilter); installShadow(widget, helper, SideTop); installShadow(widget, helper, SideBottom); widget->removeEventFilter(&_addEventFilter); } //____________________________________________________________________________________ void FrameShadowFactory::removeShadows(QWidget *widget) { widget->removeEventFilter(this); const QList children = widget->children(); for (QObject *child : children) { if (FrameShadow *shadow = qobject_cast(child)) { shadow->hide(); shadow->setParent(nullptr); shadow->deleteLater(); } } } //____________________________________________________________________________________ void FrameShadowFactory::updateShadowsGeometry(const QObject *object, QRect rect) const { const QList &children = object->children(); for (QObject *child : children) { if (FrameShadow *shadow = qobject_cast(child)) { shadow->updateGeometry(rect); } } } //____________________________________________________________________________________ void FrameShadowFactory::raiseShadows(QObject *object) const { const QList &children = object->children(); for (QObject *child : children) { if (FrameShadow *shadow = qobject_cast(child)) { shadow->raise(); } } } //____________________________________________________________________________________ void FrameShadowFactory::update(QObject *object) const { const QList &children = object->children(); for (QObject *child : children) { if (FrameShadow *shadow = qobject_cast(child)) { shadow->update(); } } } //____________________________________________________________________________________ void FrameShadowFactory::updateState(const QWidget *widget, bool focus, bool hover, qreal opacity, AnimationMode mode) const { const QList &children = widget->children(); for (QObject *child : children) { if (FrameShadow *shadow = qobject_cast(child)) { shadow->updateState(focus, hover, opacity, mode); } } } //____________________________________________________________________________________ void FrameShadowFactory::installShadow(QWidget *widget, Helper &helper, Side area) const { FrameShadow *shadow(nullptr); shadow = new FrameShadow(area, helper); shadow->setParent(widget); shadow->hide(); } //____________________________________________________________________________________ void FrameShadowFactory::widgetDestroyed(QObject *object) { _registeredWidgets.remove(object); } //____________________________________________________________________________________ FrameShadow::FrameShadow(Side area, Helper &helper) : _helper(helper) , _area(area) { setAttribute(Qt::WA_OpaquePaintEvent, false); setFocusPolicy(Qt::NoFocus); setAttribute(Qt::WA_TransparentForMouseEvents, true); setContextMenuPolicy(Qt::NoContextMenu); // grab viewport widget QWidget *viewport(this->viewport()); // set cursor from viewport if (viewport) { setCursor(viewport->cursor()); } } //____________________________________________________________________________________ void FrameShadow::updateGeometry(QRect rect) { // show on first call if (isHidden()) { show(); } // store offsets between passed rect and parent widget rect QRect parentRect(parentWidget()->contentsRect()); _margins = QMargins(rect.left() - parentRect.left(), rect.top() - parentRect.top(), rect.right() - parentRect.right(), rect.bottom() - parentRect.bottom()); // for efficiency, take out the part for which nothing is rendered rect.adjust(1, 1, -1, -1); // adjust geometry const int shadowSize(Metrics::Frame_FrameRadius); switch (_area) { case SideTop: rect.setHeight(shadowSize); break; case SideBottom: rect.setTop(rect.bottom() - shadowSize + 1); break; case SideLeft: rect.setWidth(shadowSize); rect.adjust(0, shadowSize, 0, -shadowSize); break; case SideRight: rect.setLeft(rect.right() - shadowSize + 1); rect.adjust(0, shadowSize, 0, -shadowSize); break; default: return; } setGeometry(rect); } //____________________________________________________________________________________ void FrameShadow::updateState(bool focus, bool hover, qreal opacity, AnimationMode mode) { bool changed(false); if (_hasFocus != focus) { _hasFocus = focus; changed |= true; } if (_mouseOver != hover) { _mouseOver = hover; changed |= !_hasFocus; } if (_mode != mode) { _mode = mode; changed |= (_mode == AnimationNone) || (_mode == AnimationFocus) || (_mode == AnimationHover && !_hasFocus); } if (_opacity != opacity) { _opacity = opacity; changed |= (_mode != AnimationNone); } if (changed) { if (QWidget *viewport = this->viewport()) { // need to disable viewport updates to avoid some redundant painting // besides it fixes one visual glitch (from Qt) in QTableViews viewport->setUpdatesEnabled(false); update(); viewport->setUpdatesEnabled(true); } else { update(); } } } //____________________________________________________________________________________ void FrameShadow::paintEvent(QPaintEvent *event) { // this fixes shadows in frames that change frameStyle() after polish() if (QFrame *frame = qobject_cast(parentWidget())) { if (frame->frameStyle() != (QFrame::StyledPanel | QFrame::Sunken)) { return; } } const QRect parentRect(parentWidget()->contentsRect().translated(mapFromParent(QPoint(0, 0)))); const QRect rect(parentRect.adjusted(_margins.left(), _margins.top(), _margins.right(), _margins.bottom())); // render QPainter painter(this); painter.setClipRegion(event->region()); painter.setRenderHint(QPainter::Antialiasing); const QColor outline(_helper.frameOutlineColor(palette(), _mouseOver, _hasFocus, _opacity, _mode)); painter.setCompositionMode(QPainter::CompositionMode_SourceOver); _helper.renderFrame(&painter, rect, QColor(), outline); } //____________________________________________________________________________________ QWidget *FrameShadow::viewport() const { if (!parentWidget()) { return nullptr; } else if (QAbstractScrollArea *widget = qobject_cast(parentWidget())) { return widget->viewport(); } else { return nullptr; } } }