/*
SPDX-FileCopyrightText: 2016 The Qt Company Ltd.
SPDX-FileCopyrightText: 2017 Marco Martin
SPDX-FileCopyrightText: 2017 David Edmundson
SPDX-FileCopyrightText: 2019 David Redondo
SPDX-FileCopyrightText: 2023 ivan tkachenko
SPDX-License-Identifier: LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KFQF-Accepted-GPL OR LicenseRef-Qt-Commercial
*/
#include "kquickstyleitem_p.h"
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
std::unique_ptr KQuickStyleItem::s_style = nullptr;
QStyle *KQuickStyleItem::style()
{
if (s_style) {
return s_style.get();
} else {
return qApp->style();
}
}
static bool isKeyFocusReason(Qt::FocusReason reason)
{
return reason == Qt::TabFocusReason || reason == Qt::BacktabFocusReason || reason == Qt::ShortcutFocusReason;
}
KQuickStyleItem::KQuickStyleItem(QQuickItem *parent)
: QQuickItem(parent)
, m_styleoption(nullptr)
, m_itemType(Undefined)
, m_sunken(false)
, m_raised(false)
, m_flat(false)
, m_active(true)
, m_selected(false)
, m_focus(false)
, m_hover(false)
, m_on(false)
, m_horizontal(true)
, m_transient(false)
, m_sharedWidget(false)
, m_minimum(0)
, m_maximum(100)
, m_value(0)
, m_step(0)
, m_paintMargins(0)
, m_contentWidth(0)
, m_contentHeight(0)
, m_textureWidth(0)
, m_textureHeight(0)
, m_focusReason(Qt::NoFocusReason)
{
// Check that we really are a QApplication before attempting to access the
// application style.
//
// Functions used in this file which are safe to access through qApp are:
//
// qApp->font() and qApp->fontChanged() - provided by QGuiApplication
// qApp->font("classname") - provided by QApplication but safe
// qApp->layoutDirection() - provided by QGuiApplication
// qApp->wheelScrollLines() - uses styleHints() provided by QGuiApplication
// qApp->testAttribute() and qApp->setAttribute() - provided by QCoreApplication
// qApp->devicePixelRatio() - provided by QGuiApplication
// qApp->palette("classname") - provided by QApplication but safe
//
// but any use of qApp->style(), even if only trying to test that it exists,
// will assert. Use KQuickStyleItem::style() as above.
//
// Any use of any other function provided by QApplication must either be checked
// to ensure that it is safe to use if not a QApplication, or guarded.
if (qobject_cast(QCoreApplication::instance())) {
// We are a QApplication. Unfortunately there is no styleChanged signal
// available, and it only sends QEvent::StyleChange events to all QWidgets.
// So watch for the existing application style being destroyed, which means
// that a new one is being applied. See QApplication::setStyle().
QStyle *style = qApp->style();
if (style) {
connect(style, &QObject::destroyed, this, &KQuickStyleItem::styleChanged);
}
} else if (!s_style) {
// We are not a QApplication. Create an internal copy of the configured
// desktop style, to be used for metrics, options and painting.
KSharedConfig::Ptr kdeglobals = KSharedConfig::openConfig();
KConfigGroup cg(kdeglobals, QStringLiteral("KDE"));
const QString defaultStyleName = QStringLiteral("Fusion");
s_style.reset(QStyleFactory::create(cg.readEntry("widgetStyle", defaultStyleName)));
if (!s_style) {
s_style.reset(QStyleFactory::create(defaultStyleName));
// Prevent inevitable crashes on nullptr dereference
if (!s_style) {
qWarning() << "org.kde.desktop: Could not find any QStyle such as Breeze or Fusion";
::exit(EXIT_FAILURE);
}
}
}
m_font = qApp->font();
setFlag(QQuickItem::ItemHasContents, true);
setSmooth(false);
qGuiApp->installEventFilter(this);
Kirigami::Platform::TabletModeWatcher::self()->addWatcher(this);
}
KQuickStyleItem::~KQuickStyleItem()
{
if (const QStyleOptionButton *aux = qstyleoption_cast(m_styleoption)) {
delete aux;
} else if (const QStyleOptionViewItem *aux = qstyleoption_cast(m_styleoption)) {
delete aux;
} else if (const QStyleOptionHeader *aux = qstyleoption_cast(m_styleoption)) {
delete aux;
} else if (const QStyleOptionToolButton *aux = qstyleoption_cast(m_styleoption)) {
delete aux;
} else if (const QStyleOptionToolBar *aux = qstyleoption_cast(m_styleoption)) {
delete aux;
} else if (const QStyleOptionTab *aux = qstyleoption_cast(m_styleoption)) {
delete aux;
} else if (const QStyleOptionFrame *aux = qstyleoption_cast(m_styleoption)) {
delete aux;
} else if (const QStyleOptionFocusRect *aux = qstyleoption_cast(m_styleoption)) {
delete aux;
} else if (const QStyleOptionTabWidgetFrame *aux = qstyleoption_cast(m_styleoption)) {
delete aux;
} else if (const QStyleOptionMenuItem *aux = qstyleoption_cast(m_styleoption)) {
delete aux;
} else if (const QStyleOptionComboBox *aux = qstyleoption_cast(m_styleoption)) {
delete aux;
} else if (const QStyleOptionSpinBox *aux = qstyleoption_cast(m_styleoption)) {
delete aux;
} else if (const QStyleOptionSlider *aux = qstyleoption_cast(m_styleoption)) {
delete aux;
} else if (const QStyleOptionProgressBar *aux = qstyleoption_cast(m_styleoption)) {
delete aux;
} else if (const QStyleOptionGroupBox *aux = qstyleoption_cast(m_styleoption)) {
delete aux;
} else {
delete m_styleoption;
}
m_styleoption = nullptr;
Kirigami::Platform::TabletModeWatcher::self()->removeWatcher(this);
}
void KQuickStyleItem::initStyleOption()
{
if (!m_theme) {
m_theme = static_cast(qmlAttachedPropertiesObject(this, true));
Q_ASSERT(m_theme);
connect(m_theme, &Kirigami::Platform::PlatformTheme::colorsChanged, this, [this]() {
// we need to reset the palette event if Qt::AA_SetPalette attribute has been set
if (!m_styleoption) {
return;
}
m_styleoption->palette = m_theme->palette();
polish();
});
}
Q_ASSERT(m_theme);
if (m_styleoption) {
m_styleoption->state = {};
}
QString sizeHint = m_hints.value(QStringLiteral("size")).toString();
bool needsResolvePalette = true;
bool preventMirroring = false;
switch (m_itemType) {
case Button: {
if (!m_styleoption) {
m_styleoption = new QStyleOptionButton();
}
QStyleOptionButton *opt = qstyleoption_cast(m_styleoption);
opt->text = text();
if (m_iconDirty || opt->icon.isNull()) {
opt->icon = iconFromIconProperty();
m_iconDirty = false;
}
auto iconSize = QSize(m_properties[QStringLiteral("iconWidth")].toInt(), m_properties[QStringLiteral("iconHeight")].toInt());
if (iconSize.isEmpty()) {
int e = KQuickStyleItem::style()->pixelMetric(QStyle::PM_ButtonIconSize, m_styleoption, nullptr);
if (iconSize.width() <= 0) {
iconSize.setWidth(e);
}
if (iconSize.height() <= 0) {
iconSize.setHeight(e);
}
}
opt->iconSize = iconSize;
opt->features = activeControl() == QLatin1String("default") ? QStyleOptionButton::DefaultButton : QStyleOptionButton::None;
if (m_flat) {
opt->features |= QStyleOptionButton::Flat;
}
const QFont font = qApp->font("QPushButton");
opt->fontMetrics = QFontMetrics(font);
bool hasMenu = m_properties[QStringLiteral("menu")].toBool();
if (hasMenu) {
opt->features |= QStyleOptionButton::HasMenu;
}
break;
}
case ItemRow: {
if (!m_styleoption) {
m_styleoption = new QStyleOptionViewItem();
}
QStyleOptionViewItem *opt = qstyleoption_cast(m_styleoption);
opt->features = {};
if (activeControl() == QLatin1String("alternate")) {
opt->features |= QStyleOptionViewItem::Alternate;
}
break;
}
case Splitter: {
// Use defaults
break;
}
case Item: {
if (!m_styleoption) {
m_styleoption = new QStyleOptionViewItem();
}
QStyleOptionViewItem *opt = qstyleoption_cast(m_styleoption);
opt->features = QStyleOptionViewItem::HasDisplay;
opt->text = text();
opt->textElideMode = Qt::ElideRight;
opt->displayAlignment = Qt::AlignLeft | Qt::AlignVCenter;
opt->decorationAlignment = Qt::AlignCenter;
resolvePalette();
needsResolvePalette = false;
QPalette pal = m_styleoption->palette;
pal.setBrush(QPalette::Base, Qt::NoBrush);
m_styleoption->palette = pal;
const QFont font = qApp->font("QAbstractItemView");
opt->font = font;
opt->fontMetrics = QFontMetrics(font);
break;
}
case ItemBranchIndicator: {
if (!m_styleoption) {
m_styleoption = new QStyleOption;
}
if (m_properties.value(QStringLiteral("isItem")).toBool()) {
m_styleoption->state = QStyle::State_Item;
}
if (m_properties.value(QStringLiteral("hasChildren")).toBool()) {
m_styleoption->state |= QStyle::State_Children;
}
if (m_properties.value(QStringLiteral("hasSibling")).toBool()) { // Even this one could go away
m_styleoption->state |= QStyle::State_Sibling;
}
if (m_on) {
m_styleoption->state |= QStyle::State_Open;
}
break;
}
case Header: {
if (!m_styleoption) {
m_styleoption = new QStyleOptionHeader();
}
QStyleOptionHeader *opt = qstyleoption_cast(m_styleoption);
opt->text = text();
opt->textAlignment = static_cast(m_properties.value(QStringLiteral("textalignment")).toInt());
opt->sortIndicator = activeControl() == QLatin1String("down") ? QStyleOptionHeader::SortDown
: activeControl() == QLatin1String("up") ? QStyleOptionHeader::SortUp
: QStyleOptionHeader::None;
QString headerpos = m_properties.value(QStringLiteral("headerpos")).toString();
if (headerpos == QLatin1String("beginning")) {
opt->position = QStyleOptionHeader::Beginning;
} else if (headerpos == QLatin1String("end")) {
opt->position = QStyleOptionHeader::End;
} else if (headerpos == QLatin1String("only")) {
opt->position = QStyleOptionHeader::OnlyOneSection;
} else {
opt->position = QStyleOptionHeader::Middle;
}
opt->orientation = m_properties.value(QStringLiteral("orientation")).value();
if (opt->orientation == Qt::Orientation(0)) {
opt->orientation = Qt::Horizontal;
}
const QFont font = qApp->font("QHeaderView");
opt->fontMetrics = QFontMetrics(font);
break;
}
case DelayButton:
case ToolButton: {
if (!m_styleoption) {
m_styleoption = new QStyleOptionToolButton();
}
QStyleOptionToolButton *opt = qstyleoption_cast(m_styleoption);
opt->subControls = QStyle::SC_ToolButton;
if (m_flat) {
opt->state |= QStyle::State_AutoRaise;
}
opt->activeSubControls = QStyle::SC_ToolButton;
opt->text = text();
if (m_iconDirty || opt->icon.isNull()) {
opt->icon = iconFromIconProperty();
m_iconDirty = false;
}
auto iconSize = QSize(m_properties[QStringLiteral("iconWidth")].toInt(), m_properties[QStringLiteral("iconHeight")].toInt());
if (iconSize.isEmpty()) {
const auto metric = m_flat ? QStyle::PM_ToolBarIconSize : QStyle::PM_ButtonIconSize;
int e = KQuickStyleItem::style()->pixelMetric(metric, m_styleoption, nullptr);
if (iconSize.width() <= 0) {
iconSize.setWidth(e);
}
if (iconSize.height() <= 0) {
iconSize.setHeight(e);
}
}
opt->iconSize = iconSize;
if (m_properties.value(QStringLiteral("menu")).toBool()) {
opt->features |= QStyleOptionToolButton::HasMenu;
}
const int toolButtonStyle = m_properties.value(QStringLiteral("toolButtonStyle")).toInt();
switch (toolButtonStyle) {
case Qt::ToolButtonIconOnly:
case Qt::ToolButtonTextOnly:
case Qt::ToolButtonTextBesideIcon:
case Qt::ToolButtonTextUnderIcon:
case Qt::ToolButtonFollowStyle:
opt->toolButtonStyle = (Qt::ToolButtonStyle)toolButtonStyle;
break;
default:
opt->toolButtonStyle = Qt::ToolButtonFollowStyle;
}
const QFont font = m_font.isCopyOf(qApp->font()) ? qApp->font("QToolButton") : m_font;
opt->font = font;
opt->fontMetrics = QFontMetrics(font);
break;
}
case ToolBar: {
if (!m_styleoption) {
m_styleoption = new QStyleOptionToolBar();
}
break;
}
case Tab: {
if (!m_styleoption) {
m_styleoption = new QStyleOptionTab();
}
QStyleOptionTab *opt = qstyleoption_cast(m_styleoption);
opt->text = text();
if (m_iconDirty || opt->icon.isNull()) {
opt->icon = iconFromIconProperty();
m_iconDirty = false;
}
auto iconSize = QSize(m_properties[QStringLiteral("iconWidth")].toInt(), m_properties[QStringLiteral("iconHeight")].toInt());
if (iconSize.isEmpty()) {
int e = KQuickStyleItem::style()->pixelMetric(QStyle::PM_TabBarIconSize, m_styleoption, nullptr);
if (iconSize.width() <= 0) {
iconSize.setWidth(e);
}
if (iconSize.height() <= 0) {
iconSize.setHeight(e);
}
}
opt->iconSize = iconSize;
if (m_properties.value(QStringLiteral("hasFrame")).toBool()) {
opt->features |= QStyleOptionTab::HasFrame;
}
QString orientation = m_properties.value(QStringLiteral("orientation")).toString();
QString position = m_properties.value(QStringLiteral("tabpos")).toString();
QString selectedPosition = m_properties.value(QStringLiteral("selectedpos")).toString();
opt->shape = orientation == QLatin1String("Bottom") ? QTabBar::RoundedSouth : QTabBar::RoundedNorth;
if (position == QLatin1String("beginning")) {
opt->position = QStyleOptionTab::Beginning;
} else if (position == QLatin1String("end")) {
opt->position = QStyleOptionTab::End;
} else if (position == QLatin1String("only")) {
opt->position = QStyleOptionTab::OnlyOneTab;
} else {
opt->position = QStyleOptionTab::Middle;
}
if (selectedPosition == QLatin1String("next")) {
opt->selectedPosition = QStyleOptionTab::NextIsSelected;
} else if (selectedPosition == QLatin1String("previous")) {
opt->selectedPosition = QStyleOptionTab::PreviousIsSelected;
} else {
opt->selectedPosition = QStyleOptionTab::NotAdjacent;
}
break;
}
case Frame: {
if (!m_styleoption) {
m_styleoption = new QStyleOptionFrame();
}
QStyleOptionFrame *opt = qstyleoption_cast(m_styleoption);
opt->frameShape = QFrame::StyledPanel;
opt->lineWidth = KQuickStyleItem::style()->pixelMetric(QStyle::PM_DefaultFrameWidth, m_styleoption, nullptr);
opt->midLineWidth = opt->lineWidth;
break;
}
case FocusRect: {
if (!m_styleoption) {
m_styleoption = new QStyleOptionFocusRect();
}
// Needed on windows
m_styleoption->state |= QStyle::State_KeyboardFocusChange;
break;
}
case TabFrame: {
if (!m_styleoption) {
m_styleoption = new QStyleOptionTabWidgetFrame();
}
QStyleOptionTabWidgetFrame *opt = qstyleoption_cast(m_styleoption);
opt->selectedTabRect = m_properties[QStringLiteral("selectedTabRect")].toRect();
opt->shape = m_properties[QStringLiteral("orientation")] == Qt::BottomEdge ? QTabBar::RoundedSouth : QTabBar::RoundedNorth;
if (minimum()) {
opt->selectedTabRect = QRect(value(), 0, minimum(), height());
}
opt->tabBarSize = QSize(minimum(), height());
// oxygen style needs this hack
opt->leftCornerWidgetSize = QSize(value(), 0);
break;
}
case MenuBar:
if (!m_styleoption) {
QStyleOptionMenuItem *menuOpt = new QStyleOptionMenuItem();
menuOpt->menuItemType = QStyleOptionMenuItem::EmptyArea;
m_styleoption = menuOpt;
}
break;
case MenuBarItem: {
if (!m_styleoption) {
m_styleoption = new QStyleOptionMenuItem();
}
QStyleOptionMenuItem *opt = qstyleoption_cast(m_styleoption);
opt->text = text();
opt->menuItemType = QStyleOptionMenuItem::Normal;
setProperty("_q_showUnderlined", m_hints[QStringLiteral("showUnderlined")].toBool());
const QFont font = qApp->font("QMenuBar");
opt->font = font;
opt->fontMetrics = QFontMetrics(font);
m_font = opt->font;
break;
}
case Menu: {
if (!m_styleoption) {
m_styleoption = new QStyleOptionMenuItem();
}
break;
}
case MenuItem:
case ComboBoxItem: {
if (!m_styleoption) {
m_styleoption = new QStyleOptionMenuItem();
}
QStyleOptionMenuItem *opt = qstyleoption_cast(m_styleoption);
// For GTK style. See below, in setElementType()
setProperty("_q_isComboBoxPopupItem", m_itemType == ComboBoxItem);
KQuickStyleItem::MenuItemType type = static_cast(m_properties[QStringLiteral("type")].toInt());
if (type == KQuickStyleItem::ScrollIndicatorType) {
int scrollerDirection = m_properties[QStringLiteral("scrollerDirection")].toInt();
opt->menuItemType = QStyleOptionMenuItem::Scroller;
opt->state |= scrollerDirection == Qt::UpArrow ? QStyle::State_UpArrow : QStyle::State_DownArrow;
} else if (type == KQuickStyleItem::SeparatorType) {
opt->menuItemType = QStyleOptionMenuItem::Separator;
} else {
opt->text = text();
if (type == KQuickStyleItem::MenuType) {
opt->menuItemType = QStyleOptionMenuItem::SubMenu;
} else {
opt->menuItemType = QStyleOptionMenuItem::Normal;
QString shortcut = m_properties[QStringLiteral("shortcut")].toString();
if (!shortcut.isEmpty()) {
opt->text += QLatin1Char('\t') + shortcut;
opt->reservedShortcutWidth = qMax(opt->reservedShortcutWidth, qRound(textWidth(shortcut)));
}
if (m_properties[QStringLiteral("checkable")].toBool()) {
opt->checked = on();
QVariant exclusive = m_properties[QStringLiteral("exclusive")];
opt->checkType = exclusive.toBool() ? QStyleOptionMenuItem::Exclusive : QStyleOptionMenuItem::NonExclusive;
}
}
if (m_iconDirty || opt->icon.isNull()) {
opt->icon = iconFromIconProperty();
m_iconDirty = false;
}
setProperty("_q_showUnderlined", m_hints[QStringLiteral("showUnderlined")].toBool());
const QFont font = qApp->font(m_itemType == ComboBoxItem ? "QComboMenuItem" : "QMenu");
opt->font = font;
opt->fontMetrics = QFontMetrics(font);
m_font = opt->font;
}
break;
}
case CheckBox:
case RadioButton: {
if (!m_styleoption) {
m_styleoption = new QStyleOptionButton();
}
QStyleOptionButton *opt = qstyleoption_cast(m_styleoption);
if (!on()) {
opt->state |= QStyle::State_Off;
}
if (m_properties.value(QStringLiteral("partiallyChecked")).toBool()) {
opt->state |= QStyle::State_NoChange;
}
opt->text = text();
if (m_iconDirty || opt->icon.isNull()) {
opt->icon = iconFromIconProperty();
m_iconDirty = false;
}
auto iconSize = QSize(m_properties[QStringLiteral("iconWidth")].toInt(), m_properties[QStringLiteral("iconHeight")].toInt());
if (iconSize.isEmpty()) {
int e = KQuickStyleItem::style()->pixelMetric(QStyle::PM_ButtonIconSize, m_styleoption, nullptr);
if (iconSize.width() <= 0) {
iconSize.setWidth(e);
}
if (iconSize.height() <= 0) {
iconSize.setHeight(e);
}
}
opt->iconSize = iconSize;
break;
}
case Edit: {
if (!m_styleoption) {
m_styleoption = new QStyleOptionFrame();
}
QStyleOptionFrame *opt = qstyleoption_cast(m_styleoption);
opt->lineWidth = qMax(1, KQuickStyleItem::style()->pixelMetric(QStyle::PM_DefaultFrameWidth, m_styleoption, nullptr)); // this must be non zero
break;
}
case ComboBox: {
if (!m_styleoption) {
m_styleoption = new QStyleOptionComboBox();
}
QStyleOptionComboBox *opt = qstyleoption_cast(m_styleoption);
const QFont font = qApp->font("QPushButton"); // DAVE - QQC1 code does this, but if you look at QComboBox this doesn't make sense
opt->fontMetrics = QFontMetrics(font);
opt->currentText = text();
opt->editable = m_properties[QStringLiteral("editable")].toBool();
const QVariant icon = m_properties[QStringLiteral("currentIcon")];
if (icon.canConvert()) {
opt->currentIcon = icon.value();
} else if (icon.canConvert()) {
opt->currentIcon = m_theme->iconFromTheme(icon.value(), m_properties[QStringLiteral("iconColor")].value());
}
auto iconSize = QSize(m_properties[QStringLiteral("iconWidth")].toInt(), m_properties[QStringLiteral("iconHeight")].toInt());
if (iconSize.isEmpty()) {
int e = KQuickStyleItem::style()->pixelMetric(QStyle::PM_ButtonIconSize, m_styleoption, nullptr);
if (iconSize.width() <= 0) {
iconSize.setWidth(e);
}
if (iconSize.height() <= 0) {
iconSize.setHeight(e);
}
}
opt->iconSize = iconSize;
opt->frame = !m_flat;
break;
}
case SpinBox: {
if (!m_styleoption) {
m_styleoption = new QStyleOptionSpinBox();
}
QStyleOptionSpinBox *opt = qstyleoption_cast(m_styleoption);
opt->frame = true;
if (value() & 0x1) {
opt->activeSubControls = QStyle::SC_SpinBoxUp;
} else if (value() & (1 << 1)) {
opt->activeSubControls = QStyle::SC_SpinBoxDown;
} else {
opt->activeSubControls = QStyle::SC_None;
}
opt->subControls = QStyle::SC_All;
opt->stepEnabled = {};
if (value() & (1 << 2)) {
opt->stepEnabled |= QAbstractSpinBox::StepUpEnabled;
}
if (value() & (1 << 3)) {
opt->stepEnabled |= QAbstractSpinBox::StepDownEnabled;
}
break;
}
case Slider:
case Dial: {
if (!m_styleoption) {
m_styleoption = new QStyleOptionSlider();
}
QStyleOptionSlider *opt = qstyleoption_cast(m_styleoption);
opt->orientation = horizontal() ? Qt::Horizontal : Qt::Vertical;
opt->upsideDown = !horizontal();
int min = minimum();
int max = std::max(min, maximum());
opt->minimum = min;
opt->maximum = max;
opt->sliderPosition = value();
opt->singleStep = step();
if (opt->singleStep) {
qreal numOfSteps = (opt->maximum - opt->minimum) / opt->singleStep;
// at least 5 pixels between tick marks
qreal extent = horizontal() ? width() : height();
if (numOfSteps && (extent / numOfSteps < 5)) {
opt->tickInterval = qRound((5 * numOfSteps / extent) + 0.5) * step();
} else {
opt->tickInterval = opt->singleStep;
}
} else { // default Qt-components implementation
opt->tickInterval = opt->maximum != opt->minimum ? 1200 / (opt->maximum - opt->minimum) : 0;
}
opt->sliderValue = value();
opt->subControls = QStyle::SC_SliderGroove | QStyle::SC_SliderHandle;
opt->tickPosition = activeControl() == QLatin1String("ticks") ? QSlider::TicksBelow : QSlider::NoTicks;
if (opt->tickPosition != QSlider::NoTicks) {
opt->subControls |= QStyle::SC_SliderTickmarks;
}
opt->activeSubControls = QStyle::SC_SliderHandle;
break;
}
case ProgressBar: {
if (!m_styleoption) {
m_styleoption = new QStyleOptionProgressBar();
}
QStyleOptionProgressBar *opt = qstyleoption_cast(m_styleoption);
if (horizontal()) {
opt->state |= QStyle::State_Horizontal;
} else {
opt->state &= ~QStyle::State_Horizontal;
}
opt->minimum = qMax(0, minimum());
opt->maximum = qMax(0, maximum());
opt->progress = value();
break;
}
case GroupBox: {
if (!m_styleoption) {
m_styleoption = new QStyleOptionGroupBox();
}
QStyleOptionGroupBox *opt = qstyleoption_cast(m_styleoption);
opt->text = text();
opt->lineWidth = KQuickStyleItem::style()->pixelMetric(QStyle::PM_DefaultFrameWidth, m_styleoption, nullptr);
opt->subControls = QStyle::SC_GroupBoxLabel;
opt->features = {};
if (m_properties[QStringLiteral("sunken")].toBool()) { // Qt draws an ugly line here so I ignore it
opt->subControls |= QStyle::SC_GroupBoxFrame;
} else {
opt->features |= QStyleOptionFrame::Flat;
}
if (m_properties[QStringLiteral("checkable")].toBool()) {
opt->subControls |= QStyle::SC_GroupBoxCheckBox;
}
break;
}
case ScrollBar: {
if (!m_styleoption) {
m_styleoption = new QStyleOptionSlider();
}
QStyleOptionSlider *opt = qstyleoption_cast(m_styleoption);
opt->minimum = qMax(0, minimum());
opt->maximum = qMax(0, maximum());
opt->pageStep = qMax(0, int(horizontal() ? width() : height()));
opt->orientation = horizontal() ? Qt::Horizontal : Qt::Vertical;
if (horizontal()) {
// mirrored horizontal scrollbars are not something you want to interact with
preventMirroring = true;
}
opt->sliderPosition = value();
opt->sliderValue = value();
opt->activeSubControls = (activeControl() == QLatin1String("up")) ? QStyle::SC_ScrollBarSubLine
: (activeControl() == QLatin1String("down")) ? QStyle::SC_ScrollBarAddLine
: (activeControl() == QLatin1String("handle")) ? QStyle::SC_ScrollBarSlider
: hover() ? QStyle::SC_ScrollBarGroove
: QStyle::SC_None;
if (raised()) {
opt->state |= QStyle::State_On;
}
opt->subControls = QStyle::SC_All;
setTransient(KQuickStyleItem::style()->styleHint(QStyle::SH_ScrollBar_Transient, m_styleoption));
break;
}
default:
break;
}
if (!m_styleoption) {
m_styleoption = new QStyleOption();
}
if (needsResolvePalette) {
resolvePalette();
}
m_styleoption->styleObject = this;
const auto mirror = m_control == nullptr ? qApp->layoutDirection() == Qt::RightToLeft : m_control->property("mirrored").toBool();
m_styleoption->direction = (mirror && !preventMirroring) ? Qt::RightToLeft : Qt::LeftToRight;
int w = m_textureWidth > 0 ? m_textureWidth : width();
int h = m_textureHeight > 0 ? m_textureHeight : height();
m_styleoption->rect = QRect(m_paintMargins, 0, w - 2 * m_paintMargins, h);
if (isEnabled()) {
m_styleoption->state |= QStyle::State_Enabled;
m_styleoption->palette.setCurrentColorGroup(QPalette::Active);
} else {
m_styleoption->palette.setCurrentColorGroup(QPalette::Disabled);
}
if (m_active) {
m_styleoption->state |= QStyle::State_Active;
} else {
m_styleoption->palette.setCurrentColorGroup(QPalette::Inactive);
}
if (m_sunken) {
m_styleoption->state |= QStyle::State_Sunken;
}
if (!m_sunken || m_raised) {
m_styleoption->state |= QStyle::State_Raised;
}
if (m_selected) {
m_styleoption->state |= QStyle::State_Selected;
}
if (m_focus) {
m_styleoption->state |= QStyle::State_HasFocus;
}
if (m_on) {
m_styleoption->state |= QStyle::State_On;
}
if (m_hover) {
m_styleoption->state |= QStyle::State_MouseOver;
}
if (m_horizontal) {
m_styleoption->state |= QStyle::State_Horizontal;
}
// some styles don't draw a focus rectangle if
// QStyle::State_KeyboardFocusChange is not set
if (window()) {
if (isKeyFocusReason(m_focusReason)) {
m_styleoption->state |= QStyle::State_KeyboardFocusChange;
}
}
if (sizeHint == QLatin1String("mini")) {
m_styleoption->state |= QStyle::State_Mini;
} else if (sizeHint == QLatin1String("small")) {
m_styleoption->state |= QStyle::State_Small;
}
}
QIcon KQuickStyleItem::iconFromIconProperty() const
{
QIcon icon;
const QVariant iconProperty = m_properties[QStringLiteral("icon")];
switch (iconProperty.userType()) {
case QMetaType::QIcon:
icon = iconProperty.value();
break;
case QMetaType::QUrl:
case QMetaType::QString: {
QString iconSource = iconProperty.toString();
if (iconSource.startsWith(QLatin1String("qrc:/"))) {
iconSource = iconSource.mid(3);
} else if (iconSource.startsWith(QLatin1String("file:/"))) {
iconSource = QUrl(iconSource).toLocalFile();
}
if (iconSource.contains(QLatin1String("/"))) {
icon = QIcon(iconSource);
} else {
icon = m_theme->iconFromTheme(iconSource, m_properties[QStringLiteral("iconColor")].value());
}
break;
}
default:
break;
}
return icon;
}
const char *KQuickStyleItem::classNameForItem() const
{
switch (m_itemType) {
case Button:
return "QPushButton";
case RadioButton:
return "QRadioButton";
case CheckBox:
return "QCheckBox";
case ComboBox:
return "QComboBox";
case ComboBoxItem:
return "QComboMenuItem";
case ToolBar:
return "";
case DelayButton:
case ToolButton:
return "QToolButton";
case Tab:
return "QTabButton";
case TabFrame:
return "QTabBar";
case Edit:
return "QTextEdit";
case GroupBox:
return "QGroupBox";
case Header:
return "QHeaderView";
case Item:
case ItemRow:
return "QAbstractItemView";
case Menu:
case MenuItem:
return "QMenu";
case MenuBar:
case MenuBarItem:
return "QMenuBar";
default:
return "";
}
Q_UNREACHABLE();
}
void KQuickStyleItem::resolvePalette()
{
if (QCoreApplication::testAttribute(Qt::AA_SetPalette)) {
return;
}
// In theory here we should conside m_control->property("palette")
// Whether the client code did set a custom palette color, but we can't read and write it
// from c++ anymore as is not a QPalette but a wrapper type we don't have access to,
// this kind of binding will need to be done on QML side
m_styleoption->palette = m_theme->palette();
}
int KQuickStyleItem::topPadding() const
{
return padding(Qt::TopEdge);
}
int KQuickStyleItem::leftPadding() const
{
return padding(Qt::LeftEdge);
}
int KQuickStyleItem::rightPadding() const
{
return padding(Qt::RightEdge);
}
int KQuickStyleItem::bottomPadding() const
{
return padding(Qt::BottomEdge);
}
int KQuickStyleItem::padding(Qt::Edge edge) const
{
if (m_itemType == Frame) {
const QRect cr = KQuickStyleItem::style()->subElementRect(QStyle::SE_ShapedFrameContents, m_styleoption);
switch (edge) {
case Qt::TopEdge:
return cr.top() - m_styleoption->rect.top();
case Qt::LeftEdge:
return cr.left() - m_styleoption->rect.left();
case Qt::RightEdge:
return m_styleoption->rect.right() - cr.right();
case Qt::BottomEdge:
return m_styleoption->rect.bottom() - cr.bottom();
}
}
return 0;
}
/*
* Property style
*
* Returns a simplified style name.
*
* QMacStyle = "mac"
* QWindowsXPStyle = "windowsxp"
* QFusionStyle = "fusion"
*/
QString KQuickStyleItem::styleName() const
{
const QString fullName = QString::fromLatin1(KQuickStyleItem::style()->metaObject()->className());
QStringView shortName = fullName;
if (shortName.startsWith(QLatin1Char('q'), Qt::CaseInsensitive)) {
shortName = shortName.sliced(1);
}
const QLatin1String suffix("style");
if (shortName.endsWith(suffix, Qt::CaseInsensitive)) {
shortName.chop(suffix.length());
}
return shortName.toString().toLower();
}
QString KQuickStyleItem::hitTest(int px, int py)
{
QStyle::SubControl subcontrol = QStyle::SC_All;
switch (m_itemType) {
case SpinBox: {
subcontrol = KQuickStyleItem::style()->hitTestComplexControl(QStyle::CC_SpinBox,
qstyleoption_cast(m_styleoption),
QPoint(px, py),
nullptr);
if (subcontrol == QStyle::SC_SpinBoxUp) {
return QStringLiteral("up");
} else if (subcontrol == QStyle::SC_SpinBoxDown) {
return QStringLiteral("down");
}
break;
}
case Slider: {
subcontrol = KQuickStyleItem::style()->hitTestComplexControl(QStyle::CC_Slider,
qstyleoption_cast(m_styleoption),
QPoint(px, py),
nullptr);
if (subcontrol == QStyle::SC_SliderHandle) {
return QStringLiteral("handle");
}
break;
}
case ScrollBar: {
subcontrol = KQuickStyleItem::style()->hitTestComplexControl(QStyle::CC_ScrollBar,
qstyleoption_cast(m_styleoption),
QPoint(px, py),
nullptr);
switch (subcontrol) {
case QStyle::SC_ScrollBarSlider:
return QStringLiteral("handle");
case QStyle::SC_ScrollBarSubLine:
return QStringLiteral("up");
case QStyle::SC_ScrollBarSubPage:
return QStringLiteral("upPage");
case QStyle::SC_ScrollBarAddLine:
return QStringLiteral("down");
case QStyle::SC_ScrollBarAddPage:
return QStringLiteral("downPage");
default:
break;
}
break;
}
default:
break;
}
return QStringLiteral("none");
}
QRect KQuickStyleItem::computeBoundingRect(const QList &rects)
{
const auto region = std::accumulate(rects.begin(), rects.end(), QRegion());
return region.boundingRect();
}
QSize KQuickStyleItem::sizeFromContents(int width, int height)
{
initStyleOption();
QSize size;
switch (m_itemType) {
case RadioButton:
case CheckBox: {
auto contentType = (m_itemType == RadioButton) ? QStyle::CT_RadioButton : QStyle::CT_CheckBox;
QStyleOptionButton *btn = qstyleoption_cast(m_styleoption);
QSize contentSize = btn->fontMetrics.size(Qt::TextShowMnemonic, btn->text);
if (!btn->icon.isNull()) {
contentSize.setWidth(contentSize.width() + btn->iconSize.width());
contentSize.setHeight(std::max(contentSize.height(), btn->iconSize.height()));
}
size = KQuickStyleItem::style()->sizeFromContents(contentType, m_styleoption, contentSize);
break;
}
case ToolBar:
size = QSize(200, styleName().contains(QLatin1String("windows")) ? 30 : 42);
break;
case DelayButton:
case ToolButton: {
QStyleOptionToolButton *btn = qstyleoption_cast(m_styleoption);
int w = 0;
int h = 0;
if (btn->toolButtonStyle != Qt::ToolButtonTextOnly) {
QSize icon = btn->iconSize;
if (btn->toolButtonStyle == Qt::ToolButtonIconOnly || !btn->icon.isNull()) {
w = icon.width();
h = icon.height();
}
}
if (btn->toolButtonStyle != Qt::ToolButtonIconOnly) {
QSize textSize = btn->fontMetrics.size(Qt::TextShowMnemonic, btn->text);
textSize.setWidth(textSize.width() + btn->fontMetrics.horizontalAdvance(QLatin1Char(' ')) * 2);
if (btn->toolButtonStyle == Qt::ToolButtonTextUnderIcon) {
h += 4 + textSize.height();
if (textSize.width() > w) {
w = textSize.width();
}
} else if (btn->toolButtonStyle == Qt::ToolButtonTextBesideIcon) {
w += 4 + textSize.width();
if (textSize.height() > h) {
h = textSize.height();
}
} else { // TextOnly
w = textSize.width();
h = textSize.height();
}
}
btn->rect.setSize(QSize(w, h));
size = KQuickStyleItem::style()->sizeFromContents(QStyle::CT_ToolButton, m_styleoption, QSize(w, h));
break;
}
case Button: {
QStyleOptionButton *btn = qstyleoption_cast(m_styleoption);
int contentWidth = btn->fontMetrics.boundingRect(btn->text).width();
int contentHeight = btn->fontMetrics.height();
if (!btn->icon.isNull()) {
//+4 matches a hardcoded value in QStyle and acts as a margin between the icon and the text.
contentWidth += btn->iconSize.width() + 4;
contentHeight = qMax(btn->fontMetrics.height(), btn->iconSize.height());
}
int newWidth = qMax(width, contentWidth);
int newHeight = qMax(height, contentHeight);
size = KQuickStyleItem::style()->sizeFromContents(QStyle::CT_PushButton, m_styleoption, QSize(newWidth, newHeight));
break;
}
case ComboBox: {
QStyleOptionComboBox *opt = qstyleoption_cast(m_styleoption);
int newWidth = qMax(width, m_contentWidth) + (opt->currentIcon.isNull() ? 0 : opt->iconSize.width());
int newHeight = qMax(height, opt->fontMetrics.height());
size = KQuickStyleItem::style()->sizeFromContents(QStyle::CT_ComboBox, m_styleoption, QSize(newWidth, newHeight));
break;
}
case Tab: {
QStyleOptionTab *tab = qstyleoption_cast(m_styleoption);
int contentWidth = tab->fontMetrics.boundingRect(tab->text).width();
int contentHeight = tab->fontMetrics.height();
if (!tab->icon.isNull()) {
//+4 matches a hardcoded value in QStyle and acts as a margin between the icon and the text.
contentWidth += tab->iconSize.width() + 4;
contentHeight = qMax(contentHeight, tab->iconSize.height());
}
contentWidth += KQuickStyleItem::style()->pixelMetric(QStyle::PM_TabBarTabHSpace, tab);
contentHeight += KQuickStyleItem::style()->pixelMetric(QStyle::PM_TabBarTabVSpace, tab);
const int newWidth = qMax(width, contentWidth);
const int newHeight = qMax(height, contentHeight);
size = KQuickStyleItem::style()->sizeFromContents(QStyle::CT_TabBarTab, m_styleoption, QSize(newWidth, newHeight));
break;
}
case Slider: {
const auto opt = qstyleoption_cast(m_styleoption);
const auto tickPosition = opt->tickPosition;
// Copied from QSlider
const int SliderLength = 84, TickSpace = 5;
int thick = style()->pixelMetric(QStyle::PM_SliderThickness, m_styleoption, nullptr);
if (tickPosition & QSlider::TicksAbove) {
thick += TickSpace;
}
if (tickPosition & QSlider::TicksBelow) {
thick += TickSpace;
}
int w = thick;
int h = SliderLength;
if (opt->orientation == Qt::Horizontal) {
w = SliderLength;
h = thick;
}
size = KQuickStyleItem::style()->sizeFromContents(QStyle::CT_Slider, m_styleoption, QSize(w, h));
break;
}
case ProgressBar:
size = KQuickStyleItem::style()->sizeFromContents(QStyle::CT_ProgressBar, m_styleoption, QSize(width, height));
break;
case ScrollBar: {
const auto opt = qstyleoption_cast(m_styleoption);
// Copied from QScrollBar
const int scrollBarExtent = KQuickStyleItem::style()->pixelMetric(QStyle::PM_ScrollBarExtent, opt, nullptr);
const int scrollBarSliderMin = KQuickStyleItem::style()->pixelMetric(QStyle::PM_ScrollBarSliderMin, opt, nullptr);
if (opt->orientation == Qt::Horizontal) {
size = QSize(scrollBarExtent * 2 + scrollBarSliderMin, scrollBarExtent);
} else {
size = QSize(scrollBarExtent, scrollBarExtent * 2 + scrollBarSliderMin);
}
size = KQuickStyleItem::style()->sizeFromContents(QStyle::CT_ScrollBar, opt, size);
break;
}
case SpinBox:
case Edit: {
// We have to create a new style option since we might be calling with a QStyleOptionSpinBox
QStyleOptionFrame frame;
//+2 to be consistent with the hardcoded verticalmargin in QLineEdit
int contentHeight = frame.fontMetrics.height() + 2;
frame.state = m_styleoption->state | QStyle::State_Sunken;
frame.lineWidth = KQuickStyleItem::style()->pixelMetric(QStyle::PM_DefaultFrameWidth, m_styleoption, nullptr);
frame.rect = m_styleoption->rect;
frame.styleObject = this;
size = KQuickStyleItem::style()->sizeFromContents(QStyle::CT_LineEdit, &frame, QSize(width, qMax(height, contentHeight)));
if (m_itemType == SpinBox) {
size.setWidth(KQuickStyleItem::style()->sizeFromContents(QStyle::CT_SpinBox, m_styleoption, QSize(width + 2, height)).width());
}
break;
}
case GroupBox: {
QStyleOptionGroupBox *box = qstyleoption_cast(m_styleoption);
QFontMetrics metrics(box->fontMetrics);
int baseWidth = metrics.boundingRect(box->text + QLatin1Char(' ')).width();
int baseHeight = metrics.height() + m_contentHeight;
if (box->subControls & QStyle::SC_GroupBoxCheckBox) {
baseWidth += KQuickStyleItem::style()->pixelMetric(QStyle::PM_IndicatorWidth);
baseWidth += KQuickStyleItem::style()->pixelMetric(QStyle::PM_CheckBoxLabelSpacing);
baseHeight = qMax(baseHeight, KQuickStyleItem::style()->pixelMetric(QStyle::PM_IndicatorHeight));
}
size = KQuickStyleItem::style()->sizeFromContents(QStyle::CT_GroupBox, m_styleoption, QSize(qMax(baseWidth, m_contentWidth), baseHeight));
break;
}
case Header:
size = KQuickStyleItem::style()->sizeFromContents(QStyle::CT_HeaderSection, m_styleoption, QSize(width, height));
break;
case ItemRow:
case Item: // fall through
size = KQuickStyleItem::style()->sizeFromContents(QStyle::CT_ItemViewItem, m_styleoption, QSize(width, height));
break;
case Splitter: {
const auto hw = handleWidth();
size = KQuickStyleItem::style()->sizeFromContents(QStyle::CT_Splitter, m_styleoption, QSize(hw, hw));
break;
}
case MenuBarItem:
size = KQuickStyleItem::style()->sizeFromContents(QStyle::CT_MenuBarItem, m_styleoption, QSize(width, height));
break;
case MenuBar:
size = KQuickStyleItem::style()->sizeFromContents(QStyle::CT_MenuBar, m_styleoption, QSize(width, height));
break;
case Menu:
size = KQuickStyleItem::style()->sizeFromContents(QStyle::CT_Menu, m_styleoption, QSize(width, height));
break;
case MenuItem:
case ComboBoxItem:
if (static_cast(m_styleoption)->menuItemType == QStyleOptionMenuItem::Scroller) {
size.setHeight(KQuickStyleItem::style()->pixelMetric(QStyle::PM_MenuScrollerHeight, nullptr, nullptr));
} else {
size = KQuickStyleItem::style()->sizeFromContents(QStyle::CT_MenuItem, m_styleoption, QSize(width, height));
}
break;
default:
break;
}
return size.expandedTo(QSize(m_contentWidth, m_contentHeight));
}
int KQuickStyleItem::handleWidth() const
{
// TODO: emulate QSplitter::handleWidth using contentWidth or a custom property?
return style()->pixelMetric(QStyle::PM_SplitterWidth, nullptr);
}
qreal KQuickStyleItem::baselineOffset() const
{
QRect r;
bool ceilResult = true; // By default baseline offset rounding is done upwards
switch (m_itemType) {
case Button:
r = KQuickStyleItem::style()->subElementRect(QStyle::SE_PushButtonContents, m_styleoption);
break;
case CheckBox:
r = KQuickStyleItem::style()->subElementRect(QStyle::SE_CheckBoxContents, m_styleoption);
break;
case ComboBox:
if (const auto option = qstyleoption_cast(m_styleoption)) {
r = KQuickStyleItem::style()->subControlRect(QStyle::CC_ComboBox, option, QStyle::SC_ComboBoxEditField);
if (styleName() != QLatin1String("mac")) {
r.adjust(0, 0, 0, 1);
}
}
break;
case DelayButton:
case ToolButton:
if (const auto option = qstyleoption_cast(m_styleoption)) {
r = KQuickStyleItem::style()->subControlRect(QStyle::CC_ToolButton, option, QStyle::SC_ToolButton);
}
break;
case Edit:
r = KQuickStyleItem::style()->subElementRect(QStyle::SE_LineEditContents, m_styleoption);
break;
case ProgressBar:
r = m_styleoption->rect;
r.adjust(0, 0, 0, -2);
break;
case RadioButton:
r = KQuickStyleItem::style()->subElementRect(QStyle::SE_RadioButtonContents, m_styleoption);
break;
case Slider:
r = m_styleoption->rect;
r.adjust(0, 0, 0, -2);
break;
case SpinBox:
if (const auto option = qstyleoption_cast(m_styleoption)) {
r = KQuickStyleItem::style()->subControlRect(QStyle::CC_SpinBox, option, QStyle::SC_SpinBoxEditField);
ceilResult = false;
}
break;
default:
break;
}
if (r.height() > 0) {
const QFontMetrics &fm = m_styleoption->fontMetrics;
int surplus = r.height() - fm.height();
if ((surplus & 1) && ceilResult) {
surplus++;
}
int result = r.top() + surplus / 2 + fm.ascent();
return result;
}
return 0.;
}
void KQuickStyleItem::updateBaselineOffset()
{
const qreal baseline = baselineOffset();
if (baseline > 0) {
setBaselineOffset(baseline);
}
}
void KQuickStyleItem::setContentWidth(int arg)
{
if (m_contentWidth != arg) {
m_contentWidth = arg;
updateSizeHint();
Q_EMIT contentWidthChanged(arg);
}
}
void KQuickStyleItem::setContentHeight(int arg)
{
if (m_contentHeight != arg) {
m_contentHeight = arg;
updateSizeHint();
updateBaselineOffset();
Q_EMIT contentHeightChanged(arg);
}
}
void KQuickStyleItem::updateSizeHint()
{
QSize implicitSize = sizeFromContents(m_contentWidth, m_contentHeight);
setImplicitSize(implicitSize.width(), implicitSize.height());
}
void KQuickStyleItem::updateRect()
{
initStyleOption();
m_styleoption->rect.setWidth(width());
m_styleoption->rect.setHeight(height());
}
int KQuickStyleItem::pixelMetric(const QString &metric)
{
if (metric == QLatin1String("scrollbarExtent")) {
return KQuickStyleItem::style()->pixelMetric(QStyle::PM_ScrollBarExtent, nullptr);
} else if (metric == QLatin1String("defaultframewidth")) {
return KQuickStyleItem::style()->pixelMetric(QStyle::PM_DefaultFrameWidth, m_styleoption);
} else if (metric == QLatin1String("taboverlap")) {
return KQuickStyleItem::style()->pixelMetric(QStyle::PM_TabBarTabOverlap, nullptr);
} else if (metric == QLatin1String("tabbaseoverlap")) {
return KQuickStyleItem::style()->pixelMetric(QStyle::PM_TabBarBaseOverlap, m_styleoption);
} else if (metric == QLatin1String("tabhspace")) {
return KQuickStyleItem::style()->pixelMetric(QStyle::PM_TabBarTabHSpace, nullptr);
} else if (metric == QLatin1String("indicatorwidth")) {
return KQuickStyleItem::style()->pixelMetric(QStyle::PM_IndicatorWidth, nullptr);
} else if (metric == QLatin1String("exclusiveindicatorwidth")) {
return KQuickStyleItem::style()->pixelMetric(QStyle::PM_ExclusiveIndicatorWidth, nullptr);
} else if (metric == QLatin1String("checkboxlabelspacing")) {
return KQuickStyleItem::style()->pixelMetric(QStyle::PM_CheckBoxLabelSpacing, nullptr);
} else if (metric == QLatin1String("radiobuttonlabelspacing")) {
return KQuickStyleItem::style()->pixelMetric(QStyle::PM_RadioButtonLabelSpacing, nullptr);
} else if (metric == QLatin1String("tabvspace")) {
return KQuickStyleItem::style()->pixelMetric(QStyle::PM_TabBarTabVSpace, nullptr);
} else if (metric == QLatin1String("tabbaseheight")) {
return KQuickStyleItem::style()->pixelMetric(QStyle::PM_TabBarBaseHeight, nullptr);
} else if (metric == QLatin1String("tabvshift")) {
return KQuickStyleItem::style()->pixelMetric(QStyle::PM_TabBarTabShiftVertical, nullptr);
} else if (metric == QLatin1String("menubarhmargin")) {
return KQuickStyleItem::style()->pixelMetric(QStyle::PM_MenuBarHMargin, nullptr);
} else if (metric == QLatin1String("menubarvmargin")) {
return KQuickStyleItem::style()->pixelMetric(QStyle::PM_MenuBarVMargin, nullptr);
} else if (metric == QLatin1String("menubarpanelwidth")) {
return KQuickStyleItem::style()->pixelMetric(QStyle::PM_MenuBarPanelWidth, nullptr);
} else if (metric == QLatin1String("menubaritemspacing")) {
return KQuickStyleItem::style()->pixelMetric(QStyle::PM_MenuBarItemSpacing, nullptr);
} else if (metric == QLatin1String("spacebelowmenubar")) {
return KQuickStyleItem::style()->styleHint(QStyle::SH_MainWindow_SpaceBelowMenuBar, m_styleoption);
} else if (metric == QLatin1String("menuhmargin")) {
return KQuickStyleItem::style()->pixelMetric(QStyle::PM_MenuHMargin, nullptr);
} else if (metric == QLatin1String("menuvmargin")) {
return KQuickStyleItem::style()->pixelMetric(QStyle::PM_MenuVMargin, nullptr);
} else if (metric == QLatin1String("menupanelwidth")) {
return KQuickStyleItem::style()->pixelMetric(QStyle::PM_MenuPanelWidth, nullptr);
} else if (metric == QLatin1String("submenuoverlap")) {
return KQuickStyleItem::style()->pixelMetric(QStyle::PM_SubMenuOverlap, nullptr);
} else if (metric == QLatin1String("splitterwidth")) {
return KQuickStyleItem::style()->pixelMetric(QStyle::PM_SplitterWidth, nullptr);
} else if (metric == QLatin1String("scrollbarspacing")) {
return abs(KQuickStyleItem::style()->pixelMetric(QStyle::PM_ScrollView_ScrollBarSpacing, nullptr));
} else if (metric == QLatin1String("treeviewindentation")) {
return KQuickStyleItem::style()->pixelMetric(QStyle::PM_TreeViewIndentation, nullptr);
} else if (metric == QLatin1String("layouthorizontalspacing")) {
return KQuickStyleItem::style()->pixelMetric(QStyle::PM_LayoutHorizontalSpacing, nullptr);
} else if (metric == QLatin1String("layoutverticalspacing")) {
return KQuickStyleItem::style()->pixelMetric(QStyle::PM_LayoutVerticalSpacing, nullptr);
} else if (metric == QLatin1String("layoutleftmargin")) {
return KQuickStyleItem::style()->pixelMetric(QStyle::PM_LayoutLeftMargin, nullptr);
} else if (metric == QLatin1String("layouttopmargin")) {
return KQuickStyleItem::style()->pixelMetric(QStyle::PM_LayoutTopMargin, nullptr);
} else if (metric == QLatin1String("layoutrightmargin")) {
return KQuickStyleItem::style()->pixelMetric(QStyle::PM_LayoutRightMargin, nullptr);
} else if (metric == QLatin1String("layoutbottommargin")) {
return KQuickStyleItem::style()->pixelMetric(QStyle::PM_LayoutBottomMargin, nullptr);
}
return 0;
}
QVariant KQuickStyleItem::styleHint(const QString &metric)
{
initStyleOption();
if (metric == QLatin1String("comboboxpopup")) {
return KQuickStyleItem::style()->styleHint(QStyle::SH_ComboBox_Popup, m_styleoption);
} else if (metric == QLatin1String("highlightedTextColor")) {
return m_styleoption->palette.highlightedText().color().name();
} else if (metric == QLatin1String("textColor")) {
QPalette pal = m_styleoption->palette;
pal.setCurrentColorGroup(active() ? QPalette::Active : QPalette::Inactive);
return pal.text().color().name();
} else if (metric == QLatin1String("focuswidget")) {
return KQuickStyleItem::style()->styleHint(QStyle::SH_FocusFrame_AboveWidget);
} else if (metric == QLatin1String("tabbaralignment")) {
int result = KQuickStyleItem::style()->styleHint(QStyle::SH_TabBar_Alignment);
if (result == Qt::AlignCenter) {
return QStringLiteral("center");
}
return QStringLiteral("left");
} else if (metric == QLatin1String("externalScrollBars")) {
return KQuickStyleItem::style()->styleHint(QStyle::SH_ScrollView_FrameOnlyAroundContents);
} else if (metric == QLatin1String("scrollToClickPosition")) {
return KQuickStyleItem::style()->styleHint(QStyle::SH_ScrollBar_LeftClickAbsolutePosition);
} else if (metric == QLatin1String("activateItemOnSingleClick")) {
return KQuickStyleItem::style()->styleHint(QStyle::SH_ItemView_ActivateItemOnSingleClick);
} else if (metric == QLatin1String("submenupopupdelay")) {
return KQuickStyleItem::style()->styleHint(QStyle::SH_Menu_SubMenuPopupDelay, m_styleoption);
} else if (metric == QLatin1String("wheelScrollLines")) {
return qApp->wheelScrollLines();
} else if (metric == QLatin1String("dialogButtonsHaveIcons")) {
return KQuickStyleItem::style()->styleHint(QStyle::SH_DialogButtonBox_ButtonsHaveIcons);
}
return 0;
// Add SH_Menu_SpaceActivatesItem
}
void KQuickStyleItem::setHints(const QVariantMap &hints)
{
if (m_hints != hints) {
m_hints = hints;
initStyleOption();
updateSizeHint();
polish();
if (m_styleoption->state & QStyle::State_Mini) {
m_font.setPointSize(9.);
Q_EMIT fontChanged();
} else if (m_styleoption->state & QStyle::State_Small) {
m_font.setPointSize(11.);
Q_EMIT fontChanged();
} else {
Q_EMIT hintChanged();
}
}
}
void KQuickStyleItem::resetHints()
{
m_hints.clear();
}
void KQuickStyleItem::setFont(const QFont &font)
{
if (font == m_font) {
return;
}
m_font = font;
updateSizeHint();
polish();
Q_EMIT fontChanged();
}
void KQuickStyleItem::resetFont()
{
setFont(qApp->font());
}
void KQuickStyleItem::setElementType(const QString &str)
{
if (m_type == str) {
return;
}
m_type = str;
Q_EMIT elementTypeChanged();
if (m_styleoption) {
delete m_styleoption;
m_styleoption = nullptr;
}
// Only enable visible if the widget can animate
if (str == QLatin1String("menu")) {
m_itemType = Menu;
} else if (str == QLatin1String("menuitem")) {
m_itemType = MenuItem;
} else if (str == QLatin1String("item") || str == QLatin1String("itemrow") || str == QLatin1String("header")) {
if (str == QLatin1String("header")) {
m_itemType = Header;
} else {
m_itemType = str == QLatin1String("item") ? Item : ItemRow;
}
} else if (str == QLatin1String("itembranchindicator")) {
m_itemType = ItemBranchIndicator;
} else if (str == QLatin1String("groupbox")) {
m_itemType = GroupBox;
} else if (str == QLatin1String("tab")) {
m_itemType = Tab;
} else if (str == QLatin1String("tabframe")) {
m_itemType = TabFrame;
} else if (str == QLatin1String("comboboxitem")) {
// Gtk uses qobject cast, hence we need to separate this from menuitem
// On macOS, we temporarily use the menu item because it has more accurate
// palette.
m_itemType = ComboBoxItem;
} else if (str == QLatin1String("toolbar")) {
m_itemType = ToolBar;
} else if (str == QLatin1String("toolbutton")) {
m_itemType = ToolButton;
} else if (str == QLatin1String("slider")) {
m_itemType = Slider;
} else if (str == QLatin1String("frame")) {
m_itemType = Frame;
} else if (str == QLatin1String("combobox")) {
m_itemType = ComboBox;
} else if (str == QLatin1String("splitter")) {
m_itemType = Splitter;
} else if (str == QLatin1String("progressbar")) {
m_itemType = ProgressBar;
} else if (str == QLatin1String("button")) {
m_itemType = Button;
} else if (str == QLatin1String("checkbox")) {
m_itemType = CheckBox;
} else if (str == QLatin1String("radiobutton")) {
m_itemType = RadioButton;
} else if (str == QLatin1String("edit")) {
m_itemType = Edit;
} else if (str == QLatin1String("spinbox")) {
m_itemType = SpinBox;
} else if (str == QLatin1String("scrollbar")) {
m_itemType = ScrollBar;
} else if (str == QLatin1String("widget")) {
m_itemType = Widget;
} else if (str == QLatin1String("focusframe")) {
m_itemType = FocusFrame;
} else if (str == QLatin1String("focusrect")) {
m_itemType = FocusRect;
} else if (str == QLatin1String("dial")) {
m_itemType = Dial;
} else if (str == QLatin1String("statusbar")) {
m_itemType = StatusBar;
} else if (str == QLatin1String("machelpbutton")) {
m_itemType = MacHelpButton;
} else if (str == QLatin1String("scrollareacorner")) {
m_itemType = ScrollAreaCorner;
} else if (str == QLatin1String("menubar")) {
m_itemType = MenuBar;
} else if (str == QLatin1String("menubaritem")) {
m_itemType = MenuBarItem;
} else if (str == QLatin1String("delaybutton")) {
m_itemType = DelayButton;
} else {
m_itemType = Undefined;
}
Q_EMIT leftPaddingChanged();
Q_EMIT rightPaddingChanged();
Q_EMIT topPaddingChanged();
Q_EMIT bottomPaddingChanged();
updateSizeHint();
polish();
}
QRectF KQuickStyleItem::subControlRect(const QString &subcontrolString)
{
QStyle::SubControl subcontrol = QStyle::SC_None;
initStyleOption();
switch (m_itemType) {
case SpinBox: {
QStyle::ComplexControl control = QStyle::CC_SpinBox;
if (subcontrolString == QLatin1String("down")) {
subcontrol = QStyle::SC_SpinBoxDown;
} else if (subcontrolString == QLatin1String("up")) {
subcontrol = QStyle::SC_SpinBoxUp;
} else if (subcontrolString == QLatin1String("edit")) {
subcontrol = QStyle::SC_SpinBoxEditField;
}
return KQuickStyleItem::style()->subControlRect(control, qstyleoption_cast(m_styleoption), subcontrol);
break;
}
case Slider: {
QStyle::ComplexControl control = QStyle::CC_Slider;
if (subcontrolString == QLatin1String("handle")) {
subcontrol = QStyle::SC_SliderHandle;
} else if (subcontrolString == QLatin1String("groove")) {
subcontrol = QStyle::SC_SliderGroove;
}
return KQuickStyleItem::style()->subControlRect(control, qstyleoption_cast(m_styleoption), subcontrol);
break;
}
case ScrollBar: {
QStyle::ComplexControl control = QStyle::CC_ScrollBar;
if (subcontrolString == QLatin1String("slider")) {
subcontrol = QStyle::SC_ScrollBarSlider;
} else if (subcontrolString == QLatin1String("groove")) {
subcontrol = QStyle::SC_ScrollBarGroove;
} else if (subcontrolString == QLatin1String("handle")) {
subcontrol = QStyle::SC_ScrollBarSlider;
} else if (subcontrolString == QLatin1String("add")) {
subcontrol = QStyle::SC_ScrollBarAddPage;
} else if (subcontrolString == QLatin1String("sub")) {
subcontrol = QStyle::SC_ScrollBarSubPage;
}
return KQuickStyleItem::style()->subControlRect(control, qstyleoption_cast(m_styleoption), subcontrol);
break;
}
case ItemBranchIndicator: {
QStyleOption opt;
opt.rect = QRect(0, 0, implicitWidth(), implicitHeight());
return KQuickStyleItem::style()->subElementRect(QStyle::SE_TreeViewDisclosureItem, &opt, nullptr);
}
default:
break;
}
return QRectF();
}
void KQuickStyleItem::paint(QPainter *painter)
{
initStyleOption();
if (QStyleOptionMenuItem *opt = qstyleoption_cast(m_styleoption)) {
painter->setFont(opt->font);
} else {
QFont font;
if (m_styleoption->state & QStyle::State_Mini) {
font = qApp->font("QMiniFont");
} else if (m_styleoption->state & QStyle::State_Small) {
font = qApp->font("QSmallFont");
} else {
font = QApplication::font(classNameForItem());
}
painter->setFont(font);
}
switch (m_itemType) {
case Button:
KQuickStyleItem::style()->drawControl(QStyle::CE_PushButton, m_styleoption, painter);
break;
case ItemRow: {
QPixmap pixmap;
// Only draw through style once
const QString pmKey = QLatin1String("itemrow") % QString::number(m_styleoption->state, 16) % activeControl();
if (!QPixmapCache::find(pmKey, &pixmap) || pixmap.width() < width() || height() != pixmap.height()) {
int newSize = width();
pixmap = QPixmap(newSize, height());
pixmap.fill(Qt::transparent);
QPainter pixpainter(&pixmap);
KQuickStyleItem::style()->drawPrimitive(QStyle::PE_PanelItemViewRow, m_styleoption, &pixpainter);
if ((styleName() == QLatin1String("mac") || !KQuickStyleItem::style()->styleHint(QStyle::SH_ItemView_ShowDecorationSelected)) && selected()) {
QPalette pal = QApplication::palette("QAbstractItemView");
pal.setCurrentColorGroup(m_styleoption->palette.currentColorGroup());
pixpainter.fillRect(m_styleoption->rect, pal.highlight());
}
QPixmapCache::insert(pmKey, pixmap);
}
painter->drawPixmap(0, 0, pixmap);
break;
}
case Item:
KQuickStyleItem::style()->drawControl(QStyle::CE_ItemViewItem, m_styleoption, painter);
break;
case ItemBranchIndicator:
KQuickStyleItem::style()->drawPrimitive(QStyle::PE_IndicatorBranch, m_styleoption, painter);
break;
case Header:
KQuickStyleItem::style()->drawControl(QStyle::CE_Header, m_styleoption, painter);
break;
case ToolButton:
KQuickStyleItem::style()->drawComplexControl(QStyle::CC_ToolButton, qstyleoption_cast(m_styleoption), painter);
break;
case Tab: {
if (!isKeyFocusReason(m_focusReason)) {
m_styleoption->state &= ~QStyle::State_HasFocus;
}
KQuickStyleItem::style()->drawControl(QStyle::CE_TabBarTab, m_styleoption, painter);
break;
}
case Frame:
m_styleoption->state |= QStyle::State_Sunken;
m_styleoption->state &= ~QStyle::State_Raised;
KQuickStyleItem::style()->drawControl(QStyle::CE_ShapedFrame, m_styleoption, painter);
break;
case FocusFrame:
KQuickStyleItem::style()->drawControl(QStyle::CE_FocusFrame, m_styleoption, painter);
break;
case FocusRect:
KQuickStyleItem::style()->drawPrimitive(QStyle::PE_FrameFocusRect, m_styleoption, painter);
break;
case TabFrame:
KQuickStyleItem::style()->drawPrimitive(QStyle::PE_FrameTabWidget, m_styleoption, painter);
break;
case MenuBar:
KQuickStyleItem::style()->drawControl(QStyle::CE_MenuBarEmptyArea, m_styleoption, painter);
break;
case MenuBarItem:
KQuickStyleItem::style()->drawControl(QStyle::CE_MenuBarItem, m_styleoption, painter);
break;
case MenuItem:
case ComboBoxItem: { // fall through
QStyle::ControlElement menuElement =
static_cast(m_styleoption)->menuItemType == QStyleOptionMenuItem::Scroller ? QStyle::CE_MenuScroller : QStyle::CE_MenuItem;
KQuickStyleItem::style()->drawControl(menuElement, m_styleoption, painter);
break;
}
case CheckBox:
KQuickStyleItem::style()->drawControl(QStyle::CE_CheckBox, m_styleoption, painter);
break;
case RadioButton:
KQuickStyleItem::style()->drawControl(QStyle::CE_RadioButton, m_styleoption, painter);
break;
case Edit:
KQuickStyleItem::style()->drawPrimitive(QStyle::PE_PanelLineEdit, m_styleoption, painter);
break;
case MacHelpButton:
// Not managed as mac is not supported
break;
case Widget:
KQuickStyleItem::style()->drawPrimitive(QStyle::PE_Widget, m_styleoption, painter);
break;
case ScrollAreaCorner:
KQuickStyleItem::style()->drawPrimitive(QStyle::PE_PanelScrollAreaCorner, m_styleoption, painter);
break;
case Splitter:
KQuickStyleItem::style()->drawControl(QStyle::CE_Splitter, m_styleoption, painter);
break;
case ComboBox: {
KQuickStyleItem::style()->drawComplexControl(QStyle::CC_ComboBox, qstyleoption_cast(m_styleoption), painter);
// This is needed on macOS as it will use the painter color and ignore the palette
QPen pen = painter->pen();
painter->setPen(m_styleoption->palette.text().color());
KQuickStyleItem::style()->drawControl(QStyle::CE_ComboBoxLabel, m_styleoption, painter);
painter->setPen(pen);
break;
}
case SpinBox:
#ifdef Q_OS_MAC
// macstyle depends on the embedded qlineedit to fill the editfield background
if (styleName() == QLatin1String("mac")) {
QRect editRect = KQuickStyleItem::style()->subControlRect(QStyle::CC_SpinBox,
qstyleoption_cast(m_styleoption),
QStyle::SC_SpinBoxEditField);
painter->fillRect(editRect.adjusted(-1, -1, 1, 1), m_styleoption->palette.base());
}
#endif
KQuickStyleItem::style()->drawComplexControl(QStyle::CC_SpinBox, qstyleoption_cast(m_styleoption), painter);
break;
case Slider:
KQuickStyleItem::style()->drawComplexControl(QStyle::CC_Slider, qstyleoption_cast(m_styleoption), painter);
break;
case Dial:
KQuickStyleItem::style()->drawComplexControl(QStyle::CC_Dial, qstyleoption_cast(m_styleoption), painter);
break;
case ProgressBar:
KQuickStyleItem::style()->drawControl(QStyle::CE_ProgressBar, m_styleoption, painter);
break;
case ToolBar:
painter->fillRect(m_styleoption->rect, m_styleoption->palette.window().color());
KQuickStyleItem::style()->drawControl(QStyle::CE_ToolBar, m_styleoption, painter);
painter->save();
painter->setPen(styleName() != QLatin1String("fusion") ? m_styleoption->palette.dark().color().darker(120)
: m_styleoption->palette.window().color().lighter(107));
painter->drawLine(m_styleoption->rect.bottomLeft(), m_styleoption->rect.bottomRight());
painter->restore();
break;
case StatusBar: {
painter->fillRect(m_styleoption->rect, m_styleoption->palette.window().color());
painter->setPen(m_styleoption->palette.dark().color().darker(120));
painter->drawLine(m_styleoption->rect.topLeft(), m_styleoption->rect.topRight());
KQuickStyleItem::style()->drawPrimitive(QStyle::PE_PanelStatusBar, m_styleoption, painter);
break;
}
case GroupBox:
KQuickStyleItem::style()->drawComplexControl(QStyle::CC_GroupBox, qstyleoption_cast(m_styleoption), painter);
break;
case ScrollBar:
KQuickStyleItem::style()->drawComplexControl(QStyle::CC_ScrollBar, qstyleoption_cast(m_styleoption), painter);
break;
case Menu: {
QStyleHintReturnMask val;
KQuickStyleItem::style()->styleHint(QStyle::SH_Menu_Mask, m_styleoption, nullptr, &val);
painter->save();
painter->setClipRegion(val.region);
painter->fillRect(m_styleoption->rect, m_styleoption->palette.window());
painter->restore();
KQuickStyleItem::style()->drawPrimitive(QStyle::PE_PanelMenu, m_styleoption, painter);
if (int fw = KQuickStyleItem::style()->pixelMetric(QStyle::PM_MenuPanelWidth)) {
QStyleOptionFrame frame;
frame.state = QStyle::State_None;
frame.lineWidth = fw;
frame.midLineWidth = 0;
frame.rect = m_styleoption->rect;
frame.styleObject = this;
frame.palette = m_styleoption->palette;
KQuickStyleItem::style()->drawPrimitive(QStyle::PE_FrameMenu, &frame, painter);
}
break;
}
case DelayButton: { // Adapted from Spectacle's ProgressButton made by David Redondo.
// Draw Button without text and icon, note the missing text and icon in options
QStyleOption baseStyleOptions = *m_styleoption;
baseStyleOptions.state.setFlag(QStyle::State_Enabled, !baseStyleOptions.state.testFlag(QStyle::State_Sunken));
KQuickStyleItem::style()->drawPrimitive(QStyle::PE_PanelButtonTool, &baseStyleOptions, painter);
qreal progress = qreal(value()) / qreal(maximum());
if (!qFuzzyIsNull(progress)) {
// Draw overlay
QStyleOption overlayOption;
overlayOption.palette = m_styleoption->palette;
overlayOption.palette.setBrush(QPalette::Button, m_styleoption->palette.highlight());
overlayOption.rect = m_styleoption->direction == Qt::LeftToRight ?
QRect(0, 0, m_styleoption->rect.width() * progress, m_styleoption->rect.height())
: QRect(QPoint(m_styleoption->rect.width() * (1 - progress), 0), m_styleoption->rect.size());
overlayOption.state.setFlag(QStyle::State_Sunken, m_styleoption->state.testFlag(QStyle::State_Sunken));
painter->setCompositionMode(QPainter::CompositionMode_SourceAtop);
painter->setOpacity(0.5);
KQuickStyleItem::style()->drawPrimitive(QStyle::PE_PanelButtonTool, &overlayOption, painter);
}
// Finally draw text and icon and outline
painter->setOpacity(1);
KQuickStyleItem::style()->drawControl(QStyle::CE_ToolButtonLabel, m_styleoption, painter);
break;
}
default:
break;
}
}
qreal KQuickStyleItem::textWidth(const QString &text)
{
QFontMetricsF fm = QFontMetricsF(m_styleoption->fontMetrics);
return fm.boundingRect(text).width();
}
qreal KQuickStyleItem::textHeight(const QString &text)
{
QFontMetricsF fm = QFontMetricsF(m_styleoption->fontMetrics);
return text.isEmpty() ? fm.height() : fm.boundingRect(text).height();
}
QString KQuickStyleItem::elidedText(const QString &text, int elideMode, int width)
{
return m_styleoption->fontMetrics.elidedText(text, Qt::TextElideMode(elideMode), width);
}
bool KQuickStyleItem::hasThemeIcon(const QString &icon) const
{
return QIcon::hasThemeIcon(icon);
}
void KQuickStyleItem::updateFocusReason()
{
if (m_control) {
auto reason = m_focusReasonProperty.read(m_control).value();
if (m_focusReason != reason) {
m_focusReason = reason;
polish();
}
}
}
bool KQuickStyleItem::event(QEvent *ev)
{
if (ev->type() == QEvent::StyleAnimationUpdate) {
if (isVisible()) {
ev->accept();
polish();
}
return true;
} else if (ev->type() == Kirigami::Platform::TabletModeChangedEvent::type) {
Q_EMIT leftPaddingChanged();
Q_EMIT rightPaddingChanged();
Q_EMIT topPaddingChanged();
Q_EMIT bottomPaddingChanged();
updateSizeHint();
polish();
return true;
}
return QQuickItem::event(ev);
}
void KQuickStyleItem::setTextureWidth(int w)
{
if (m_textureWidth == w) {
return;
}
m_textureWidth = w;
Q_EMIT textureWidthChanged(m_textureWidth);
update();
}
void KQuickStyleItem::setTextureHeight(int h)
{
if (m_textureHeight == h) {
return;
}
m_textureHeight = h;
Q_EMIT textureHeightChanged(m_textureHeight);
update();
}
QQuickItem *KQuickStyleItem::control() const
{
return m_control;
}
void KQuickStyleItem::setControl(QQuickItem *control)
{
if (control == m_control) {
return;
}
if (m_control) {
m_control->removeEventFilter(this);
disconnect(m_control, nullptr, this, nullptr);
}
m_control = control;
m_focusReasonProperty = {};
m_focusReason = Qt::NoFocusReason;
if (m_control) {
// focusReasonChanged is a signal of QQuickControl class which is not exposed.
const auto mo = m_control->metaObject();
m_focusReasonProperty = mo->property(mo->indexOfProperty("focusReason"));
if (m_focusReasonProperty.isValid()) {
// wish there was something like QMetaMethod::fromSignal but for slots
const auto slot = metaObject()->method(metaObject()->indexOfSlot("updateFocusReason()"));
// also wish there was a way to connect QMetaMethod to arbitrary member function
connect(m_control, m_focusReasonProperty.notifySignal(), this, slot);
updateFocusReason();
}
m_control->installEventFilter(this);
if (m_control->window()) {
m_window = m_control->window();
m_window->installEventFilter(this);
}
connect(m_control, &QQuickItem::windowChanged, this, [this](QQuickWindow *window) {
if (m_window) {
m_window->removeEventFilter(this);
}
m_window = window;
if (m_window) {
m_window->installEventFilter(this);
}
});
}
Q_EMIT controlChanged();
}
QSGNode *KQuickStyleItem::updatePaintNode(QSGNode *node, UpdatePaintNodeData *)
{
if (m_image.isNull()) {
delete node;
return nullptr;
}
QSGNinePatchNode *styleNode = static_cast(node);
if (!styleNode) {
styleNode = window()->createNinePatchNode();
}
#ifdef QSG_RUNTIME_DESCRIPTION
qsgnode_set_description(styleNode, QString::fromLatin1("%1:%2, '%3'").arg(styleName(), elementType(), text()));
#endif
styleNode->setTexture(window()->createTextureFromImage(m_image, QQuickWindow::TextureCanUseAtlas));
auto scale = window()->effectiveDevicePixelRatio();
QRectF bounds = QRectF(boundingRect().topLeft(), QSizeF{m_image.width() / scale, m_image.height() / scale});
QPointF globalPixelPos = mapToScene(bounds.topLeft()) * scale;
QPointF posAdjust = QPointF(globalPixelPos.x() - std::round(globalPixelPos.x()), globalPixelPos.y() - std::round(globalPixelPos.y()))
/ scale;
bounds.moveTopLeft(bounds.topLeft() - posAdjust);
styleNode->setBounds(bounds);
styleNode->setDevicePixelRatio(scale);
styleNode->setPadding(m_border.left(), m_border.top(), m_border.right(), m_border.bottom());
styleNode->update();
return styleNode;
}
void KQuickStyleItem::updatePolish()
{
if (isVisible() && width() >= 1 && height() >= 1) { // Note these are reals so 1 pixel is minimum
#if QT_VERSION < QT_VERSION_CHECK(6, 7, 3)
// Need to request the focus reason every time, the signal is not entirely reliable: https://bugreports.qt.io/browse/QTBUG-125725
updateFocusReason();
#endif
const qreal devicePixelRatio = window() ? window()->effectiveDevicePixelRatio() : qApp->devicePixelRatio();
const QSize size = QSize(m_textureWidth > 0 ? m_textureWidth : width(), m_textureHeight > 0 ? m_textureHeight : height()) * devicePixelRatio;
if (m_image.size() != size) {
m_image = QImage(size, QImage::Format_ARGB32_Premultiplied);
}
m_image.setDevicePixelRatio(devicePixelRatio);
m_image.fill(Qt::transparent);
QPainter painter(&m_image);
painter.setLayoutDirection(qApp->layoutDirection());
paint(&painter);
QQuickItem::update();
} else if (!m_image.isNull()) {
m_image = QImage();
QQuickItem::update();
}
}
bool KQuickStyleItem::eventFilter(QObject *watched, QEvent *event)
{
if (watched == m_control) {
// Page accepts mouse events without doing anything with them (for a workaround wrt dragging from empty areas of flickables) when the interaction is
// pure mouse, steal events from them, so a parent handler can initiate a window drag from empty areas, either Kirigami.ApplicationWindow or the breeze
// style from a QQwuickwidget
if (event->type() == QEvent::MouseButtonPress) {
QMouseEvent *me = static_cast(event);
if (me->source() == Qt::MouseEventNotSynthesized && watched->inherits("QQuickPage")) {
event->setAccepted(false);
return true;
}
}
} else if (watched == m_window.data()) {
if (event->type() == QEvent::KeyPress || event->type() == QEvent::KeyRelease) {
QKeyEvent *ke = static_cast(event);
if (ke->key() == Qt::Key_Alt) {
polish();
}
}
} else if (watched == qGuiApp) {
if (event->type() == QEvent::ApplicationFontChange) {
QMetaObject::invokeMethod(this, &KQuickStyleItem::updateSizeHint, Qt::QueuedConnection);
}
}
return QQuickItem::eventFilter(watched, event);
}
void KQuickStyleItem::itemChange(QQuickItem::ItemChange change, const QQuickItem::ItemChangeData &value)
{
if (change == QQuickItem::ItemVisibleHasChanged || change == QQuickItem::ItemEnabledHasChanged || change == QQuickItem::ItemDevicePixelRatioHasChanged) {
polish();
}
QQuickItem::itemChange(change, value);
}
void KQuickStyleItem::geometryChange(const QRectF &newGeometry, const QRectF &oldGeometry)
{
QQuickItem::geometryChange(newGeometry, oldGeometry);
if (!newGeometry.isEmpty() && newGeometry != oldGeometry) {
polish();
updateRect();
if (newGeometry.height() != oldGeometry.height()) {
updateBaselineOffset();
}
}
}
void KQuickStyleItem::styleChanged()
{
// It should be safe to use qApp->style() unguarded here, because the signal
// will only have been connected if qApp is a QApplication.
Q_ASSERT(qobject_cast(QCoreApplication::instance()));
auto *style = qApp->style();
if (!style || QCoreApplication::closingDown()) {
return;
}
Q_ASSERT(style != sender());
connect(style, &QObject::destroyed, this, &KQuickStyleItem::styleChanged);
updateSizeHint();
polish();
Q_EMIT styleNameChanged();
}
QPixmap QQuickTableRowImageProvider1::requestPixmap(const QString &id, QSize *size, const QSize &requestedSize)
{
Q_UNUSED(requestedSize);
int width = 16;
int height = 16;
if (size) {
*size = QSize(width, height);
}
QPixmap pixmap(width, height);
QStyleOptionViewItem opt;
opt.state |= QStyle::State_Enabled;
opt.rect = QRect(0, 0, width, height);
QString style = QString::fromLatin1(KQuickStyleItem::style()->metaObject()->className());
opt.features = {};
if (id.contains(QLatin1String("selected"))) {
opt.state |= QStyle::State_Selected;
}
if (id.contains(QLatin1String("active"))) {
opt.state |= QStyle::State_Active;
opt.palette.setCurrentColorGroup(QPalette::Active);
} else {
opt.palette.setCurrentColorGroup(QPalette::Inactive);
}
if (id.contains(QLatin1String("alternate"))) {
opt.features |= QStyleOptionViewItem::Alternate;
}
QPalette pal = QApplication::palette("QAbstractItemView");
if (opt.state & QStyle::State_Selected
&& (style.contains(QLatin1String("Mac")) || !KQuickStyleItem::style()->styleHint(QStyle::SH_ItemView_ShowDecorationSelected))) {
pal.setCurrentColorGroup(opt.palette.currentColorGroup());
pixmap.fill(pal.highlight().color());
} else {
pixmap.fill(pal.base().color());
QPainter pixpainter(&pixmap);
KQuickStyleItem::style()->drawPrimitive(QStyle::PE_PanelItemViewRow, &opt, &pixpainter);
}
return pixmap;
}
#include "moc_kquickpadding_p.cpp"
#include "moc_kquickstyleitem_p.cpp"