File indexing completed on 2024-05-26 16:53:42
0001 /* 0002 SPDX-FileCopyrightText: 2003-2007 Fredrik Höglund <fredrik@kde.org> 0003 SPDX-FileCopyrightText: 2019 Kai Uwe Broulik <kde@broulik.de> 0004 0005 SPDX-License-Identifier: GPL-2.0-only 0006 */ 0007 0008 #include "previewitem.h" 0009 #include "kcm_style_debug.h" 0010 0011 #include <QHoverEvent> 0012 #include <QMouseEvent> 0013 #include <QPainter> 0014 #include <QPixmapCache> 0015 #include <QQuickWindow> 0016 #include <QStyleFactory> 0017 #include <QWidget> 0018 0019 #include <KColorScheme> 0020 #include <KSharedConfig> 0021 0022 PreviewItem::PreviewItem(QQuickItem *parent) 0023 : QQuickPaintedItem(parent) 0024 { 0025 setAcceptHoverEvents(true); 0026 0027 // HACK QtCurve deadlocks on application teardown when the Q_GLOBAL_STATIC QFactoryLoader 0028 // in QStyleFactory is destroyed which destroys all loaded styles prompting QtCurve 0029 // to disconnect from DBus stalling the application. 0030 // This also happens before any of the KCM objects are destroyed, so our only chance 0031 // is cleaning up in response to aboutToQuit 0032 connect(qApp, &QApplication::aboutToQuit, this, [this] { 0033 m_style.reset(); 0034 }); 0035 } 0036 0037 PreviewItem::~PreviewItem() = default; 0038 0039 void PreviewItem::componentComplete() 0040 { 0041 QQuickPaintedItem::componentComplete(); 0042 reload(); 0043 } 0044 0045 bool PreviewItem::eventFilter(QObject *watched, QEvent *event) 0046 { 0047 if (watched == m_widget.get()) { 0048 switch (event->type()) { 0049 case QEvent::Show: 0050 case QEvent::UpdateRequest: 0051 update(); 0052 break; 0053 default: 0054 break; 0055 } 0056 } 0057 0058 return QQuickPaintedItem::eventFilter(watched, event); 0059 } 0060 0061 QString PreviewItem::styleName() const 0062 { 0063 return m_styleName; 0064 } 0065 0066 void PreviewItem::setStyleName(const QString &styleName) 0067 { 0068 if (m_styleName == styleName) { 0069 return; 0070 } 0071 0072 m_styleName = styleName; 0073 reload(); 0074 Q_EMIT styleNameChanged(); 0075 } 0076 0077 bool PreviewItem::isValid() const 0078 { 0079 return m_style && m_widget; 0080 } 0081 0082 void setStyleRecursively(QWidget *widget, QStyle *style, const QPalette &palette) 0083 { 0084 // Don't let styles kill the palette for other styles being previewed. 0085 widget->setPalette(QPalette()); 0086 widget->setPalette(palette); 0087 0088 widget->setStyle(style); 0089 0090 const auto children = widget->children(); 0091 for (QObject *child : children) { 0092 if (child->isWidgetType()) { 0093 setStyleRecursively(static_cast<QWidget *>(child), style, palette); 0094 } 0095 } 0096 } 0097 0098 void PreviewItem::reload() 0099 { 0100 if (!isComponentComplete()) { 0101 return; 0102 } 0103 0104 const bool oldValid = isValid(); 0105 0106 m_style.reset(QStyleFactory::create(m_styleName)); 0107 if (!m_style) { 0108 qCWarning(KCM_STYLE_DEBUG) << "Failed to load style" << m_styleName; 0109 if (oldValid != isValid()) { 0110 Q_EMIT validChanged(); 0111 } 0112 return; 0113 } 0114 0115 m_widget.reset(new QWidget); 0116 // Don't actually show the widget as a separate window when calling show() 0117 m_widget->setAttribute(Qt::WA_DontShowOnScreen); 0118 // Do not wait for this widget to close before the app closes 0119 m_widget->setAttribute(Qt::WA_QuitOnClose, false); 0120 0121 m_ui.setupUi(m_widget.get()); 0122 0123 // Prevent Qt from wrongly caching radio button images 0124 QPixmapCache::clear(); 0125 0126 QPalette palette(KColorScheme::createApplicationPalette(KSharedConfig::openConfig())); 0127 m_style->polish(palette); 0128 0129 // HACK Needed so the previews look like their window is active 0130 // The previews don't have a parent (we're in QML, after all, there is no QWidget* to parent it to) 0131 // so QWidget::isActiveWindow() always returns false making the widget look dull 0132 // You still won't get hover effects in some themes (those that don't do that for inactive windows) 0133 // but at least at a glance it looks fine... 0134 for (int i = 0; i < QPalette::NColorRoles; ++i) { 0135 const auto role = static_cast<QPalette::ColorRole>(i); 0136 palette.setColor(QPalette::Inactive, role, palette.color(QPalette::Active, role)); 0137 } 0138 0139 setStyleRecursively(m_widget.get(), m_style.get(), palette); 0140 0141 m_widget->ensurePolished(); 0142 0143 m_widget->resize(qRound(width()), qRound(height())); 0144 0145 m_widget->installEventFilter(this); 0146 0147 m_widget->show(); 0148 0149 const auto sizeHint = m_widget->sizeHint(); 0150 setImplicitSize(sizeHint.width(), sizeHint.height()); 0151 0152 if (oldValid != isValid()) { 0153 Q_EMIT validChanged(); 0154 } 0155 } 0156 0157 void PreviewItem::paint(QPainter *painter) 0158 { 0159 if (m_widget && m_widget->isVisible()) { 0160 m_widget->render(painter); 0161 } 0162 } 0163 0164 void PreviewItem::hoverMoveEvent(QHoverEvent *event) 0165 { 0166 sendHoverEvent(event); 0167 } 0168 0169 void PreviewItem::hoverLeaveEvent(QHoverEvent *event) 0170 { 0171 if (m_lastWidgetUnderMouse) { 0172 dispatchEnterLeave(nullptr, m_lastWidgetUnderMouse, mapToGlobal(event->pos())); 0173 m_lastWidgetUnderMouse = nullptr; 0174 } 0175 } 0176 0177 void PreviewItem::sendHoverEvent(QHoverEvent *event) 0178 { 0179 if (!m_widget || !m_widget->isVisible()) { 0180 return; 0181 } 0182 0183 QPointF pos = event->pos(); 0184 0185 QWidget *child = m_widget->childAt(pos.toPoint()); 0186 QWidget *receiver = child ? child : m_widget.get(); 0187 0188 dispatchEnterLeave(receiver, m_lastWidgetUnderMouse, mapToGlobal(event->pos())); 0189 0190 m_lastWidgetUnderMouse = receiver; 0191 0192 pos = receiver->mapFrom(m_widget.get(), pos.toPoint()); 0193 0194 QMouseEvent mouseEvent(QEvent::MouseMove, 0195 pos, 0196 receiver->mapTo(receiver->topLevelWidget(), pos.toPoint()), 0197 receiver->mapToGlobal(pos.toPoint()), 0198 Qt::NoButton, 0199 {} /*buttons*/, 0200 event->modifiers()); 0201 0202 qApp->sendEvent(receiver, &mouseEvent); 0203 0204 event->setAccepted(mouseEvent.isAccepted()); 0205 } 0206 0207 // Simplified copy of QApplicationPrivate::dispatchEnterLeave 0208 void PreviewItem::dispatchEnterLeave(QWidget *enter, QWidget *leave, const QPointF &globalPosF) 0209 { 0210 if ((!enter && !leave) || (enter == leave)) { 0211 return; 0212 } 0213 0214 QWidgetList leaveList; 0215 QWidgetList enterList; 0216 0217 bool sameWindow = leave && enter && leave->window() == enter->window(); 0218 if (leave && !sameWindow) { 0219 auto *w = leave; 0220 do { 0221 leaveList.append(w); 0222 } while (!w->isWindow() && (w = w->parentWidget())); 0223 } 0224 if (enter && !sameWindow) { 0225 auto *w = enter; 0226 do { 0227 enterList.append(w); 0228 } while (!w->isWindow() && (w = w->parentWidget())); 0229 } 0230 if (sameWindow) { 0231 int enterDepth = 0; 0232 int leaveDepth = 0; 0233 auto *e = enter; 0234 while (!e->isWindow() && (e = e->parentWidget())) 0235 enterDepth++; 0236 auto *l = leave; 0237 while (!l->isWindow() && (l = l->parentWidget())) 0238 leaveDepth++; 0239 QWidget *wenter = enter; 0240 QWidget *wleave = leave; 0241 while (enterDepth > leaveDepth) { 0242 wenter = wenter->parentWidget(); 0243 enterDepth--; 0244 } 0245 while (leaveDepth > enterDepth) { 0246 wleave = wleave->parentWidget(); 0247 leaveDepth--; 0248 } 0249 while (!wenter->isWindow() && wenter != wleave) { 0250 wenter = wenter->parentWidget(); 0251 wleave = wleave->parentWidget(); 0252 } 0253 0254 for (auto *w = leave; w != wleave; w = w->parentWidget()) 0255 leaveList.append(w); 0256 0257 for (auto *w = enter; w != wenter; w = w->parentWidget()) 0258 enterList.append(w); 0259 } 0260 0261 const QPoint globalPos = globalPosF.toPoint(); 0262 0263 QEvent leaveEvent(QEvent::Leave); 0264 for (int i = 0; i < leaveList.size(); ++i) { 0265 auto *w = leaveList.at(i); 0266 QApplication::sendEvent(w, &leaveEvent); 0267 if (w->testAttribute(Qt::WA_Hover)) { 0268 QHoverEvent he(QEvent::HoverLeave, QPoint(-1, -1), w->mapFromGlobal(globalPos), QApplication::keyboardModifiers()); 0269 QApplication::sendEvent(w, &he); 0270 } 0271 } 0272 if (!enterList.isEmpty()) { 0273 const QPoint windowPos = qAsConst(enterList).back()->window()->mapFromGlobal(globalPos); 0274 for (auto it = enterList.crbegin(), end = enterList.crend(); it != end; ++it) { 0275 auto *w = *it; 0276 const QPointF localPos = w->mapFromGlobal(globalPos); 0277 QEnterEvent enterEvent(localPos, windowPos, globalPosF); 0278 QApplication::sendEvent(w, &enterEvent); 0279 if (w->testAttribute(Qt::WA_Hover)) { 0280 QHoverEvent he(QEvent::HoverEnter, localPos, QPoint(-1, -1), QApplication::keyboardModifiers()); 0281 QApplication::sendEvent(w, &he); 0282 } 0283 } 0284 } 0285 } 0286 0287 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) 0288 void PreviewItem::geometryChanged(const QRectF &newGeometry, const QRectF &oldGeometry) 0289 #else 0290 void PreviewItem::geometryChange(const QRectF &newGeometry, const QRectF &oldGeometry) 0291 #endif 0292 { 0293 if (m_widget && newGeometry != oldGeometry) { 0294 m_widget->resize(qRound(newGeometry.width()), qRound(newGeometry.height())); 0295 } 0296 0297 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) 0298 QQuickPaintedItem::geometryChanged(newGeometry, oldGeometry); 0299 #else 0300 QQuickPaintedItem::geometryChange(newGeometry, oldGeometry); 0301 #endif 0302 }