2024-02-17 22:51:19 +00:00
|
|
|
/*
|
|
|
|
* SPDX-FileCopyrightText: 2014 Hugo Pereira Da Costa <hugo.pereira@free.fr>
|
|
|
|
*
|
|
|
|
* SPDX-License-Identifier: GPL-2.0-or-later
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include "breezeframeshadow.h"
|
|
|
|
|
|
|
|
#include "breezemetrics.h"
|
|
|
|
|
|
|
|
#include <QAbstractScrollArea>
|
|
|
|
#include <QApplication>
|
|
|
|
#include <QDebug>
|
|
|
|
#include <QFrame>
|
|
|
|
#include <QMouseEvent>
|
|
|
|
#include <QPainter>
|
|
|
|
#include <QSplitter>
|
|
|
|
|
|
|
|
#include <KColorUtils>
|
|
|
|
|
|
|
|
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<QFrame *>(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<QSplitter *>(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());
|
2024-08-09 01:20:25 +00:00
|
|
|
while (parent && !parent->isWindow()) {
|
2024-02-17 22:51:19 +00:00
|
|
|
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<QObject *> children = widget->children();
|
2024-08-09 01:20:25 +00:00
|
|
|
for (QObject *child : children) {
|
2024-02-17 22:51:19 +00:00
|
|
|
if (FrameShadow *shadow = qobject_cast<FrameShadow *>(child)) {
|
|
|
|
shadow->hide();
|
|
|
|
shadow->setParent(nullptr);
|
|
|
|
shadow->deleteLater();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//____________________________________________________________________________________
|
|
|
|
void FrameShadowFactory::updateShadowsGeometry(const QObject *object, QRect rect) const
|
|
|
|
{
|
|
|
|
const QList<QObject *> &children = object->children();
|
2024-08-09 01:20:25 +00:00
|
|
|
for (QObject *child : children) {
|
2024-02-17 22:51:19 +00:00
|
|
|
if (FrameShadow *shadow = qobject_cast<FrameShadow *>(child)) {
|
|
|
|
shadow->updateGeometry(rect);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//____________________________________________________________________________________
|
|
|
|
void FrameShadowFactory::raiseShadows(QObject *object) const
|
|
|
|
{
|
|
|
|
const QList<QObject *> &children = object->children();
|
2024-08-09 01:20:25 +00:00
|
|
|
for (QObject *child : children) {
|
2024-02-17 22:51:19 +00:00
|
|
|
if (FrameShadow *shadow = qobject_cast<FrameShadow *>(child)) {
|
|
|
|
shadow->raise();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//____________________________________________________________________________________
|
|
|
|
void FrameShadowFactory::update(QObject *object) const
|
|
|
|
{
|
|
|
|
const QList<QObject *> &children = object->children();
|
2024-08-09 01:20:25 +00:00
|
|
|
for (QObject *child : children) {
|
2024-02-17 22:51:19 +00:00
|
|
|
if (FrameShadow *shadow = qobject_cast<FrameShadow *>(child)) {
|
|
|
|
shadow->update();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//____________________________________________________________________________________
|
|
|
|
void FrameShadowFactory::updateState(const QWidget *widget, bool focus, bool hover, qreal opacity, AnimationMode mode) const
|
|
|
|
{
|
|
|
|
const QList<QObject *> &children = widget->children();
|
2024-08-09 01:20:25 +00:00
|
|
|
for (QObject *child : children) {
|
2024-02-17 22:51:19 +00:00
|
|
|
if (FrameShadow *shadow = qobject_cast<FrameShadow *>(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<QFrame *>(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<QAbstractScrollArea *>(parentWidget())) {
|
|
|
|
return widget->viewport();
|
|
|
|
|
|
|
|
} else {
|
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|