forked from Qortal/Brooklyn
295 lines
8.6 KiB
C++
295 lines
8.6 KiB
C++
/*
|
|
SPDX-FileCopyrightText: 2003-2007 Fredrik Höglund <fredrik@kde.org>
|
|
SPDX-FileCopyrightText: 2019 Kai Uwe Broulik <kde@broulik.de>
|
|
|
|
SPDX-License-Identifier: GPL-2.0-only
|
|
*/
|
|
|
|
#include "previewitem.h"
|
|
#include "kcm_style_debug.h"
|
|
|
|
#include <QHoverEvent>
|
|
#include <QMouseEvent>
|
|
#include <QPainter>
|
|
#include <QPixmapCache>
|
|
#include <QQuickWindow>
|
|
#include <QStyleFactory>
|
|
#include <QWidget>
|
|
|
|
#include <KColorScheme>
|
|
#include <KSharedConfig>
|
|
|
|
PreviewItem::PreviewItem(QQuickItem *parent)
|
|
: QQuickPaintedItem(parent)
|
|
{
|
|
setAcceptHoverEvents(true);
|
|
|
|
// HACK QtCurve deadlocks on application teardown when the Q_GLOBAL_STATIC QFactoryLoader
|
|
// in QStyleFactory is destroyed which destroys all loaded styles prompting QtCurve
|
|
// to disconnect from DBus stalling the application.
|
|
// This also happens before any of the KCM objects are destroyed, so our only chance
|
|
// is cleaning up in response to aboutToQuit
|
|
connect(qApp, &QApplication::aboutToQuit, this, [this] {
|
|
m_style.reset();
|
|
});
|
|
}
|
|
|
|
PreviewItem::~PreviewItem() = default;
|
|
|
|
void PreviewItem::componentComplete()
|
|
{
|
|
QQuickPaintedItem::componentComplete();
|
|
reload();
|
|
}
|
|
|
|
bool PreviewItem::eventFilter(QObject *watched, QEvent *event)
|
|
{
|
|
if (watched == m_widget.data()) {
|
|
switch (event->type()) {
|
|
case QEvent::Show:
|
|
case QEvent::UpdateRequest:
|
|
update();
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
return QQuickPaintedItem::eventFilter(watched, event);
|
|
}
|
|
|
|
QString PreviewItem::styleName() const
|
|
{
|
|
return m_styleName;
|
|
}
|
|
|
|
void PreviewItem::setStyleName(const QString &styleName)
|
|
{
|
|
if (m_styleName == styleName) {
|
|
return;
|
|
}
|
|
|
|
m_styleName = styleName;
|
|
reload();
|
|
Q_EMIT styleNameChanged();
|
|
}
|
|
|
|
bool PreviewItem::isValid() const
|
|
{
|
|
return m_style && m_widget;
|
|
}
|
|
|
|
void setStyleRecursively(QWidget *widget, QStyle *style, const QPalette &palette)
|
|
{
|
|
// Don't let styles kill the palette for other styles being previewed.
|
|
widget->setPalette(QPalette());
|
|
widget->setPalette(palette);
|
|
|
|
widget->setStyle(style);
|
|
|
|
const auto children = widget->children();
|
|
for (QObject *child : children) {
|
|
if (child->isWidgetType()) {
|
|
setStyleRecursively(static_cast<QWidget *>(child), style, palette);
|
|
}
|
|
}
|
|
}
|
|
|
|
void PreviewItem::reload()
|
|
{
|
|
if (!isComponentComplete()) {
|
|
return;
|
|
}
|
|
|
|
const bool oldValid = isValid();
|
|
|
|
m_style.reset(QStyleFactory::create(m_styleName));
|
|
if (!m_style) {
|
|
qCWarning(KCM_STYLE_DEBUG) << "Failed to load style" << m_styleName;
|
|
if (oldValid != isValid()) {
|
|
Q_EMIT validChanged();
|
|
}
|
|
return;
|
|
}
|
|
|
|
m_widget.reset(new QWidget);
|
|
// Don't actually show the widget as a separate window when calling show()
|
|
m_widget->setAttribute(Qt::WA_DontShowOnScreen);
|
|
// Do not wait for this widget to close before the app closes
|
|
m_widget->setAttribute(Qt::WA_QuitOnClose, false);
|
|
|
|
m_ui.setupUi(m_widget.data());
|
|
|
|
// Prevent Qt from wrongly caching radio button images
|
|
QPixmapCache::clear();
|
|
|
|
QPalette palette(KColorScheme::createApplicationPalette(KSharedConfig::openConfig()));
|
|
m_style->polish(palette);
|
|
|
|
// HACK Needed so the previews look like their window is active
|
|
// The previews don't have a parent (we're in QML, after all, there is no QWidget* to parent it to)
|
|
// so QWidget::isActiveWindow() always returns false making the widget look dull
|
|
// You still won't get hover effects in some themes (those that don't do that for inactive windows)
|
|
// but at least at a glance it looks fine...
|
|
for (int i = 0; i < QPalette::NColorRoles; ++i) {
|
|
const auto role = static_cast<QPalette::ColorRole>(i);
|
|
palette.setColor(QPalette::Inactive, role, palette.color(QPalette::Active, role));
|
|
}
|
|
|
|
setStyleRecursively(m_widget.data(), m_style.data(), palette);
|
|
|
|
m_widget->ensurePolished();
|
|
|
|
m_widget->resize(qRound(width()), qRound(height()));
|
|
|
|
m_widget->installEventFilter(this);
|
|
|
|
m_widget->show();
|
|
|
|
const auto sizeHint = m_widget->sizeHint();
|
|
setImplicitSize(sizeHint.width(), sizeHint.height());
|
|
|
|
if (oldValid != isValid()) {
|
|
Q_EMIT validChanged();
|
|
}
|
|
}
|
|
|
|
void PreviewItem::paint(QPainter *painter)
|
|
{
|
|
if (m_widget && m_widget->isVisible()) {
|
|
m_widget->render(painter);
|
|
}
|
|
}
|
|
|
|
void PreviewItem::hoverMoveEvent(QHoverEvent *event)
|
|
{
|
|
sendHoverEvent(event);
|
|
}
|
|
|
|
void PreviewItem::hoverLeaveEvent(QHoverEvent *event)
|
|
{
|
|
if (m_lastWidgetUnderMouse) {
|
|
dispatchEnterLeave(nullptr, m_lastWidgetUnderMouse, mapToGlobal(event->pos()));
|
|
m_lastWidgetUnderMouse = nullptr;
|
|
}
|
|
}
|
|
|
|
void PreviewItem::sendHoverEvent(QHoverEvent *event)
|
|
{
|
|
if (!m_widget || !m_widget->isVisible()) {
|
|
return;
|
|
}
|
|
|
|
QPointF pos = event->pos();
|
|
|
|
QWidget *child = m_widget->childAt(pos.toPoint());
|
|
QWidget *receiver = child ? child : m_widget.data();
|
|
|
|
dispatchEnterLeave(receiver, m_lastWidgetUnderMouse, mapToGlobal(event->pos()));
|
|
|
|
m_lastWidgetUnderMouse = receiver;
|
|
|
|
pos = receiver->mapFrom(m_widget.data(), pos.toPoint());
|
|
|
|
QMouseEvent mouseEvent(QEvent::MouseMove,
|
|
pos,
|
|
receiver->mapTo(receiver->topLevelWidget(), pos.toPoint()),
|
|
receiver->mapToGlobal(pos.toPoint()),
|
|
Qt::NoButton,
|
|
{} /*buttons*/,
|
|
event->modifiers());
|
|
|
|
qApp->sendEvent(receiver, &mouseEvent);
|
|
|
|
event->setAccepted(mouseEvent.isAccepted());
|
|
}
|
|
|
|
// Simplified copy of QApplicationPrivate::dispatchEnterLeave
|
|
void PreviewItem::dispatchEnterLeave(QWidget *enter, QWidget *leave, const QPointF &globalPosF)
|
|
{
|
|
if ((!enter && !leave) || (enter == leave)) {
|
|
return;
|
|
}
|
|
|
|
QWidgetList leaveList;
|
|
QWidgetList enterList;
|
|
|
|
bool sameWindow = leave && enter && leave->window() == enter->window();
|
|
if (leave && !sameWindow) {
|
|
auto *w = leave;
|
|
do {
|
|
leaveList.append(w);
|
|
} while (!w->isWindow() && (w = w->parentWidget()));
|
|
}
|
|
if (enter && !sameWindow) {
|
|
auto *w = enter;
|
|
do {
|
|
enterList.append(w);
|
|
} while (!w->isWindow() && (w = w->parentWidget()));
|
|
}
|
|
if (sameWindow) {
|
|
int enterDepth = 0;
|
|
int leaveDepth = 0;
|
|
auto *e = enter;
|
|
while (!e->isWindow() && (e = e->parentWidget()))
|
|
enterDepth++;
|
|
auto *l = leave;
|
|
while (!l->isWindow() && (l = l->parentWidget()))
|
|
leaveDepth++;
|
|
QWidget *wenter = enter;
|
|
QWidget *wleave = leave;
|
|
while (enterDepth > leaveDepth) {
|
|
wenter = wenter->parentWidget();
|
|
enterDepth--;
|
|
}
|
|
while (leaveDepth > enterDepth) {
|
|
wleave = wleave->parentWidget();
|
|
leaveDepth--;
|
|
}
|
|
while (!wenter->isWindow() && wenter != wleave) {
|
|
wenter = wenter->parentWidget();
|
|
wleave = wleave->parentWidget();
|
|
}
|
|
|
|
for (auto *w = leave; w != wleave; w = w->parentWidget())
|
|
leaveList.append(w);
|
|
|
|
for (auto *w = enter; w != wenter; w = w->parentWidget())
|
|
enterList.append(w);
|
|
}
|
|
|
|
const QPoint globalPos = globalPosF.toPoint();
|
|
|
|
QEvent leaveEvent(QEvent::Leave);
|
|
for (int i = 0; i < leaveList.size(); ++i) {
|
|
auto *w = leaveList.at(i);
|
|
QApplication::sendEvent(w, &leaveEvent);
|
|
if (w->testAttribute(Qt::WA_Hover)) {
|
|
QHoverEvent he(QEvent::HoverLeave, QPoint(-1, -1), w->mapFromGlobal(globalPos), QApplication::keyboardModifiers());
|
|
QApplication::sendEvent(w, &he);
|
|
}
|
|
}
|
|
if (!enterList.isEmpty()) {
|
|
const QPoint windowPos = qAsConst(enterList).back()->window()->mapFromGlobal(globalPos);
|
|
for (auto it = enterList.crbegin(), end = enterList.crend(); it != end; ++it) {
|
|
auto *w = *it;
|
|
const QPointF localPos = w->mapFromGlobal(globalPos);
|
|
QEnterEvent enterEvent(localPos, windowPos, globalPosF);
|
|
QApplication::sendEvent(w, &enterEvent);
|
|
if (w->testAttribute(Qt::WA_Hover)) {
|
|
QHoverEvent he(QEvent::HoverEnter, localPos, QPoint(-1, -1), QApplication::keyboardModifiers());
|
|
QApplication::sendEvent(w, &he);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void PreviewItem::geometryChanged(const QRectF &newGeometry, const QRectF &oldGeometry)
|
|
{
|
|
if (m_widget && newGeometry != oldGeometry) {
|
|
m_widget->resize(qRound(newGeometry.width()), qRound(newGeometry.height()));
|
|
}
|
|
|
|
QQuickPaintedItem::geometryChanged(newGeometry, oldGeometry);
|
|
}
|