Commit 6515aa66 authored by Noah Davis's avatar Noah Davis 🌵
Browse files

kstyle: Add QFocusFrame to non-view/delegate interactive widget

There are 2 parts that contain the bulk of the code in this patch.

Style::event() is used to apply the QFocusFrame to a widget when it gets
focus with a keyboard input related focus reason. If the focused widget
has a focusProxy, this makes sure the QFocusFrame is applied to the
focusProxy instead.

Style::drawFocusFrame() is mostly what you'd expect. It draws a focus
frame based on the type of widget the QFocusFrame was applied to. I had
to do a workaround for QFocusFrame not fully repainting ouside the
bounds of QSliders and QDials when the handle moves though. What I do is
check if `_focusFrame` is defined and then `_focusFrame->update()`.

BUG: 443469


(cherry picked from commit 586e7462)
parent 8f2d1e7b
/*
* SPDX-FileCopyrightText: 2014 Hugo Pereira Da Costa <hugo.pereira@free.fr>
*
/* SPDX-FileCopyrightText: 2014 Hugo Pereira Da Costa <hugo.pereira@free.fr>
* SPDX-FileCopyrightText: 2016 The Qt Company Ltd.
* SPDX-FileCopyrightText: 2021 Noah Davis <noahadvs@gmail.com>
* SPDX-License-Identifier: GPL-2.0-or-later
*/
......@@ -49,6 +49,8 @@
#include <QWidgetAction>
#include <QMdiArea>
#include <QDialogButtonBox>
#include <QGraphicsItem>
#include <QGraphicsProxyWidget>
#if BREEZE_HAVE_QTQUICK
#include <QQuickWindow>
......@@ -584,6 +586,9 @@ namespace Breeze
case PM_ToolBarFrameWidth: return Metrics::ToolBar_FrameWidth;
case PM_ToolTipLabelFrameWidth: return Metrics::ToolTip_FrameWidth;
case PM_FocusFrameVMargin:
case PM_FocusFrameHMargin: return 2;
// layout
case PM_LayoutLeftMargin:
case PM_LayoutTopMargin:
......@@ -760,6 +765,9 @@ namespace Breeze
case SH_ProgressDialog_CenterCancelButton: return false;
case SH_MessageBox_CenterButtons: return false;
case SH_FocusFrame_AboveWidget: return true;
case SH_FocusFrame_Mask: return false;
case SH_RequestSoftwareInputPanel: return RSIP_OnMouseClick;
case SH_TitleBar_NoBorder: return true;
case SH_DockWidget_ButtonsHaveFrame: return false;
......@@ -1033,6 +1041,7 @@ namespace Breeze
case CE_ScrollBarAddPage: fcn = &Style::emptyControl; break;
case CE_ScrollBarSubPage: fcn = &Style::emptyControl; break;
case CE_ShapedFrame: fcn = &Style::drawShapedFrameControl; break;
case CE_FocusFrame: fcn = &Style::drawFocusFrame; break;
case CE_RubberBand: fcn = &Style::drawRubberBandControl; break;
case CE_SizeGrip: fcn = &Style::emptyControl; break;
case CE_HeaderSection: fcn = &Style::drawHeaderSectionControl; break;
......@@ -1131,6 +1140,52 @@ namespace Breeze
}
bool Style::event(QEvent *e)
{
// Adapted from QMacStyle::event()
if (e->type() == QEvent::FocusIn) {
QWidget *target = nullptr;
auto focusWidget = QApplication::focusWidget();
if (auto graphicsView = qobject_cast<QGraphicsView *>(focusWidget)) {
QGraphicsItem *focusItem = graphicsView->scene() ? graphicsView->scene()->focusItem() : nullptr;
if (focusItem && focusItem->type() == QGraphicsProxyWidget::Type) {
auto proxy = static_cast<QGraphicsProxyWidget *>(focusItem);
if (proxy->widget()) {
focusWidget = proxy->widget()->focusWidget();
}
}
}
if (focusWidget) {
auto focusEvent = static_cast<QFocusEvent*>(e);
auto focusReason = focusEvent->reason();
bool hasKeyboardFocusReason = focusReason == Qt::TabFocusReason
|| focusReason == Qt::BacktabFocusReason
|| focusReason == Qt::ShortcutFocusReason;
if (hasKeyboardFocusReason) {
auto focusProxy = focusWidget->focusProxy();
while (focusProxy != nullptr) {
focusWidget = focusProxy;
focusProxy = focusWidget->focusProxy();
}
target = focusWidget;
}
}
if (_focusFrame) {
// sets to nullptr or a widget
_focusFrame->setWidget(target);
} else if (target) { // only create if there is a widget
_focusFrame = new QFocusFrame(target);
_focusFrame->setWidget(target);
}
} else if (e->type() == QEvent::FocusOut) {
if(_focusFrame) {
_focusFrame->setWidget(nullptr);
}
}
return ParentStyleClass::event(e);
}
//_____________________________________________________________________
bool Style::eventFilter( QObject *object, QEvent *event )
{
......@@ -5406,6 +5461,200 @@ namespace Breeze
}
// Adapted from QMacStylePrivate::drawFocusRing()
bool Style::drawFocusFrame( const QStyleOption* option, QPainter* painter, const QWidget* widget ) const
{
const QFocusFrame *focusFrame = qobject_cast<const QFocusFrame *>(widget);
const QWidget *targetWidget = focusFrame ? focusFrame->widget() : nullptr;
// If we have a QFocusFrame and no target widget, don't draw anything.
if (focusFrame && !targetWidget) return true;
const int hmargin = proxy()->pixelMetric(QStyle::PM_FocusFrameHMargin, option, widget);
const int vmargin = proxy()->pixelMetric(QStyle::PM_FocusFrameVMargin, option, widget);
// Not using targetWidget->rect() to get this because this should still work when widget is null.
const QRect targetWidgetRect = option->rect.adjusted(hmargin, vmargin, -hmargin, -vmargin);
QRect innerRect = targetWidgetRect;
QRect outerRect = option->rect;
qreal innerRadius = Metrics::Frame_FrameRadius;
qreal outerRadius = innerRadius + vmargin;
QPainterPath focusFramePath;
focusFramePath.setFillRule(Qt::OddEvenFill);
// If focusFrame is null, make it still possible to draw something since
// most draw functions allow drawing something when the widget is null
if (!focusFrame) {
focusFramePath.addRoundedRect(innerRect, innerRadius, innerRadius);
focusFramePath.addRoundedRect(outerRect, outerRadius, outerRadius);
} else if (targetWidget->inherits("QLineEdit") || targetWidget->inherits("QTextEdit")
|| targetWidget->inherits("QAbstractSpinBox") || targetWidget->inherits("QComboBox")
|| targetWidget->inherits("QPushButton") || targetWidget->inherits("QToolButton")
) {
/* It's OK to check for QAbstractSpinBox instead of more spacific classes
* because QAbstractSpinBox handles the painting for spinboxes,
* unlike most abstract widget classes.
*/
innerRect.adjust(1, 1, -1, -1);
focusFramePath.addRoundedRect(innerRect, innerRadius, innerRadius);
outerRect = innerRect.adjusted(-hmargin, -vmargin, hmargin, vmargin);
focusFramePath.addRoundedRect(outerRect, outerRadius, outerRadius);
} else if (auto checkBox = qobject_cast<const QCheckBox*>(targetWidget)) {
QStyleOptionButton opt;
opt.initFrom(checkBox);
if (checkBox->isDown()) {
opt.state |= QStyle::State_Sunken;
}
if (checkBox->isTristate()) {
opt.state |= QStyle::State_NoChange;
} else if (checkBox->isChecked()) {
opt.state |= QStyle::State_On;
} else {
opt.state |= QStyle::State_Off;
}
opt.text = checkBox->text();
opt.icon = checkBox->icon();
opt.iconSize = checkBox->iconSize();
innerRect = subElementRect(SE_CheckBoxIndicator, &opt, checkBox);
innerRect.adjust(2, 2, -2, -2);
innerRect.translate(hmargin, vmargin);
innerRect = visualRect(option, innerRect);
focusFramePath.addRoundedRect(innerRect, innerRadius, innerRadius);
outerRect = innerRect.adjusted(-hmargin, -vmargin, hmargin, vmargin);
focusFramePath.addRoundedRect(outerRect, outerRadius, outerRadius);
} else if (auto radioButton = qobject_cast<const QRadioButton*>(targetWidget)) {
QStyleOptionButton opt;
opt.initFrom(radioButton);
if (radioButton->isDown()) {
opt.state |= QStyle::State_Sunken;
}
if (radioButton->isChecked()) {
opt.state |= QStyle::State_On;
} else {
opt.state |= QStyle::State_Off;
}
opt.text = radioButton->text();
opt.icon = radioButton->icon();
opt.iconSize = radioButton->iconSize();
innerRect = subElementRect(SE_RadioButtonIndicator, &opt, radioButton);
innerRect.adjust(2, 2, -2, -2);
innerRect.translate(hmargin, vmargin);
innerRect = visualRect(option, innerRect);
innerRadius = innerRect.height() / 2.0;
focusFramePath.addRoundedRect(innerRect, innerRadius, innerRadius);
outerRect = innerRect.adjusted(-hmargin, -vmargin, hmargin, vmargin);
outerRadius = outerRect.height() / 2.0;
focusFramePath.addRoundedRect(outerRect, outerRadius, outerRadius);
} else if (auto slider = qobject_cast<const QSlider*>(targetWidget)) {
QStyleOptionSlider opt;
opt.initFrom(slider);
opt.orientation = slider->orientation();
opt.maximum = slider->maximum();
opt.minimum = slider->minimum();
opt.tickPosition = slider->tickPosition();
opt.tickInterval = slider->tickInterval();
if (opt.orientation == Qt::Horizontal) {
opt.upsideDown = slider->invertedAppearance() && opt.direction != Qt::RightToLeft;
} else {
opt.upsideDown = !slider->invertedAppearance();
}
opt.sliderPosition = slider->sliderPosition();
opt.sliderValue = slider->value();
opt.singleStep = slider->singleStep();
opt.pageStep = slider->pageStep();
if (opt.orientation == Qt::Horizontal) {
opt.state |= QStyle::State_Horizontal;
}
innerRect = subControlRect(CC_Slider, &opt, SC_SliderHandle, slider);
innerRect.adjust(1, 1, -1, -1);
innerRect.translate(hmargin, vmargin);
innerRadius = innerRect.height() / 2.0;
focusFramePath.addRoundedRect(innerRect, innerRadius, innerRadius);
outerRect = innerRect.adjusted(-hmargin, -vmargin, hmargin, vmargin);
outerRadius = outerRect.height() / 2.0;
focusFramePath.addRoundedRect(outerRect, outerRadius, outerRadius);
if (_focusFrame) {
// Workaround QFocusFrame not fully repainting outside the bounds of the targetWidget
auto previousRect = _focusFrame->property("_lastOuterRect").value<QRect>();
if (previousRect != outerRect) {
_focusFrame->update();
_focusFrame->setProperty("_lastOuterRect", outerRect);
}
}
} else if (auto dial = qobject_cast<const QDial*>(targetWidget)) {
QStyleOptionSlider opt;
opt.initFrom(dial);
opt.maximum = dial->maximum();
opt.minimum = dial->minimum();
opt.sliderPosition = dial->sliderPosition();
opt.sliderValue = dial->value();
opt.singleStep = dial->singleStep();
opt.pageStep = dial->pageStep();
opt.upsideDown = !dial->invertedAppearance();
opt.notchTarget = dial->notchTarget();
opt.dialWrapping = dial->wrapping();
if (!dial->notchesVisible()) {
opt.subControls &= ~QStyle::SC_DialTickmarks;
opt.tickPosition = QSlider::TicksAbove;
} else {
opt.tickPosition = QSlider::NoTicks;
}
opt.tickInterval = dial->notchSize();
innerRect = subControlRect(CC_Dial, &opt, SC_DialHandle, dial);
innerRect.adjust(1, 1, -1, -1);
innerRect.translate(hmargin, vmargin);
innerRadius = innerRect.height() / 2.0;
focusFramePath.addRoundedRect(innerRect, innerRadius, innerRadius);
outerRect = innerRect.adjusted(-hmargin, -vmargin, hmargin, vmargin);
outerRadius = outerRect.height() / 2.0;
focusFramePath.addRoundedRect(outerRect, outerRadius, outerRadius);
if (_focusFrame) {
// Workaround QFocusFrame not fully repainting outside the bounds of the targetWidget
auto previousRect = _focusFrame->property("_lastOuterRect").value<QRect>();
if (previousRect != outerRect) {
_focusFrame->update();
_focusFrame->setProperty("_lastOuterRect", outerRect);
}
}
} else if (auto groupBox = qobject_cast<const QGroupBox*>(targetWidget)) {
QStyleOptionGroupBox opt;
opt.initFrom(groupBox);
opt.lineWidth = 1;
opt.midLineWidth = 0;
opt.textAlignment = groupBox->alignment();
opt.subControls = QStyle::SC_GroupBoxFrame;
if (groupBox->isCheckable()) {
opt.subControls |= QStyle::SC_GroupBoxCheckBox;
if (groupBox->isChecked()) {
opt.state |= QStyle::State_On;
} else {
opt.state |= QStyle::State_Off;
}
// NOTE: we can't get the sunken state of the checkbox
}
opt.text = groupBox->title();
if (!opt.text.isEmpty()) {
opt.subControls |= QStyle::SC_GroupBoxLabel;
}
innerRect = subControlRect(CC_GroupBox, &opt, SC_GroupBoxCheckBox, groupBox);
innerRect.adjust(2, 2, -2, -2);
innerRect.translate(hmargin, vmargin);
innerRect = visualRect(option, innerRect);
focusFramePath.addRoundedRect(innerRect, innerRadius, innerRadius);
outerRect = innerRect.adjusted(-hmargin, -vmargin, hmargin, vmargin);
focusFramePath.addRoundedRect(outerRect, outerRadius, outerRadius);
} else {
return true;
}
auto outerColor = _helper->alphaColor(option->palette.highlight().color(), 0.33);
painter->setRenderHint(QPainter::Antialiasing);
painter->fillPath(focusFramePath, outerColor);
return true;
}
//___________________________________________________________________________________
bool Style::drawRubberBandControl( const QStyleOption* option, QPainter* painter, const QWidget* ) const
{
......
......@@ -22,6 +22,7 @@
#include <QCommandLinkButton>
#include <QCommonStyle>
#include <QDockWidget>
#include <QFocusFrame>
#include <QHash>
#include <QIcon>
#include <QMdiSubWindow>
......@@ -122,6 +123,8 @@ namespace Breeze
QPainter*, const QRect&, int alignment, const QPalette&, bool enabled,
const QString&, QPalette::ColorRole = QPalette::NoRole) const override;
bool event( QEvent* ) override;
//*@name event filters
//@{
......@@ -295,6 +298,7 @@ namespace Breeze
bool drawScrollBarAddLineControl( const QStyleOption*, QPainter*, const QWidget* ) const;
bool drawScrollBarSubLineControl( const QStyleOption*, QPainter*, const QWidget* ) const;
bool drawShapedFrameControl( const QStyleOption*, QPainter*, const QWidget* ) const;
bool drawFocusFrame( const QStyleOption*, QPainter*, const QWidget* ) const;
bool drawRubberBandControl( const QStyleOption*, QPainter*, const QWidget* ) const;
bool drawHeaderSectionControl( const QStyleOption*, QPainter*, const QWidget* ) const;
bool drawHeaderEmptyAreaControl( const QStyleOption*, QPainter*, const QWidget* ) const;
......@@ -474,6 +478,9 @@ namespace Breeze
ScrollBarButtonType _subLineButtons = SingleButton;
//@}
//* focus frame
QPointer<QFocusFrame> _focusFrame = nullptr;
//* helper
Helper* _helper = nullptr;
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment