Changeset View
Changeset View
Standalone View
Standalone View
kcms/style/previewitem.cpp
- This file was added.
1 | /* | ||||
---|---|---|---|---|---|
2 | * Copyright © 2003-2007 Fredrik Höglund <fredrik@kde.org> | ||||
3 | * Copyright 2019 Kai Uwe Broulik <kde@broulik.de> | ||||
4 | * | ||||
5 | * This program is free software; you can redistribute it and/or | ||||
6 | * modify it under the terms of the GNU General Public | ||||
7 | * License version 2 as published by the Free Software Foundation. | ||||
8 | * | ||||
9 | * This program is distributed in the hope that it will be useful, | ||||
10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | ||||
12 | * General Public License for more details. | ||||
13 | * | ||||
14 | * You should have received a copy of the GNU General Public License | ||||
15 | * along with this program; see the file COPYING. If not, write to | ||||
16 | * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, | ||||
17 | * Boston, MA 02110-1301, USA. | ||||
18 | */ | ||||
19 | | ||||
20 | #include "previewitem.h" | ||||
21 | | ||||
22 | #include <QHoverEvent> | ||||
23 | #include <QMouseEvent> | ||||
24 | #include <QPainter> | ||||
25 | #include <QPixmapCache> | ||||
26 | #include <QQuickWindow> | ||||
27 | #include <QStyleFactory> | ||||
28 | #include <QWidget> | ||||
29 | | ||||
30 | #include <KColorScheme> | ||||
31 | #include <KSharedConfig> | ||||
32 | | ||||
33 | PreviewItem::PreviewItem(QQuickItem *parent) | ||||
34 | : QQuickPaintedItem(parent) | ||||
35 | { | ||||
36 | setAcceptHoverEvents(true); | ||||
37 | | ||||
38 | // HACK QtCurve deadlocks on application teardown when the Q_GLOBAL_STATIC QFactoryLoader | ||||
39 | // in QStyleFactory is destroyed which destroys all loaded styles prompting QtCurve | ||||
40 | // to disconnect from DBus stalling the application. | ||||
41 | // This also happens before any of the KCM objects are destroyed, so our only chance | ||||
42 | // is cleaning up in response to aboutToQuit | ||||
43 | connect(qApp, &QApplication::aboutToQuit, this, [this] { | ||||
44 | m_style.reset(); | ||||
45 | }); | ||||
46 | } | ||||
47 | | ||||
48 | PreviewItem::~PreviewItem() = default; | ||||
49 | | ||||
50 | void PreviewItem::componentComplete() | ||||
51 | { | ||||
52 | QQuickPaintedItem::componentComplete(); | ||||
53 | reload(); | ||||
54 | } | ||||
55 | | ||||
56 | bool PreviewItem::eventFilter(QObject *watched, QEvent *event) | ||||
57 | { | ||||
58 | if (watched == m_widget.data()) { | ||||
59 | switch (event->type()) { | ||||
60 | case QEvent::Show: | ||||
61 | case QEvent::UpdateRequest: | ||||
62 | update(); | ||||
63 | break; | ||||
64 | default: | ||||
65 | break; | ||||
66 | } | ||||
67 | } | ||||
68 | | ||||
69 | return QQuickPaintedItem::eventFilter(watched, event); | ||||
70 | } | ||||
71 | | ||||
72 | QString PreviewItem::styleName() const | ||||
73 | { | ||||
74 | return m_styleName; | ||||
75 | } | ||||
76 | | ||||
77 | void PreviewItem::setStyleName(const QString &styleName) | ||||
78 | { | ||||
79 | if (m_styleName == styleName) { | ||||
80 | return; | ||||
81 | } | ||||
82 | | ||||
83 | m_styleName = styleName; | ||||
84 | reload(); | ||||
85 | emit styleNameChanged(); | ||||
86 | } | ||||
87 | | ||||
88 | bool PreviewItem::isValid() const | ||||
89 | { | ||||
90 | return m_style && m_widget; | ||||
91 | } | ||||
92 | | ||||
93 | void setStyleRecursively(QWidget *widget, QStyle *style) | ||||
94 | { | ||||
95 | // Don't let styles kill the palette for other styles being previewed. | ||||
96 | widget->setPalette(QPalette()); | ||||
97 | | ||||
98 | QPalette newPalette(KColorScheme::createApplicationPalette(KSharedConfig::openConfig())); | ||||
99 | style->polish(newPalette); | ||||
100 | | ||||
101 | widget->setPalette(newPalette); | ||||
102 | | ||||
103 | widget->setStyle(style); | ||||
104 | | ||||
105 | const auto children = widget->children(); | ||||
106 | for (QObject *child : children) { | ||||
107 | if (child->isWidgetType()) { | ||||
108 | setStyleRecursively(static_cast<QWidget *>(child), style); | ||||
109 | } | ||||
110 | } | ||||
111 | } | ||||
112 | | ||||
113 | void PreviewItem::reload() | ||||
114 | { | ||||
115 | if (!isComponentComplete()) { | ||||
116 | return; | ||||
117 | } | ||||
118 | | ||||
119 | const bool oldValid = isValid(); | ||||
120 | | ||||
121 | m_style.reset(QStyleFactory::create(m_styleName)); | ||||
122 | if (!m_style) { | ||||
123 | qWarning() << "Failed to load style" << m_styleName; | ||||
124 | if (oldValid != isValid()) { | ||||
125 | emit validChanged(); | ||||
126 | } | ||||
127 | return; | ||||
128 | } | ||||
129 | | ||||
130 | m_widget.reset(new QWidget); | ||||
131 | // Don't actually show the widget as a separate window when calling show() | ||||
132 | m_widget->setAttribute(Qt::WA_DontShowOnScreen); | ||||
133 | // Do not wait for this widget to close before the app closes | ||||
134 | m_widget->setAttribute(Qt::WA_QuitOnClose, false); | ||||
135 | | ||||
136 | m_ui.setupUi(m_widget.data()); | ||||
137 | | ||||
138 | // Prevent Qt from wrongly caching radio button images | ||||
139 | QPixmapCache::clear(); | ||||
140 | | ||||
141 | setStyleRecursively(m_widget.data(), m_style.data()); | ||||
142 | | ||||
143 | m_widget->ensurePolished(); | ||||
144 | | ||||
145 | const auto sizeHint = m_widget->sizeHint(); | ||||
146 | setImplicitSize(sizeHint.width(), sizeHint.height()); | ||||
147 | | ||||
148 | m_widget->resize(qRound(width()), qRound(height())); | ||||
149 | | ||||
150 | m_widget->installEventFilter(this); | ||||
151 | | ||||
152 | m_widget->show(); | ||||
153 | | ||||
154 | if (oldValid != isValid()) { | ||||
155 | emit validChanged(); | ||||
156 | } | ||||
157 | } | ||||
158 | | ||||
159 | void PreviewItem::paint(QPainter *painter) | ||||
160 | { | ||||
161 | if (m_widget && m_widget->isVisible()) { | ||||
162 | m_widget->render(painter); | ||||
163 | } | ||||
164 | } | ||||
165 | | ||||
166 | void PreviewItem::hoverMoveEvent(QHoverEvent *event) | ||||
167 | { | ||||
168 | sendHoverEvent(event); | ||||
169 | } | ||||
170 | | ||||
171 | void PreviewItem::hoverLeaveEvent(QHoverEvent *event) | ||||
172 | { | ||||
173 | if (m_lastWidgetUnderMouse) { | ||||
174 | dispatchEnterLeave(nullptr, m_lastWidgetUnderMouse, mapToGlobal(event->pos())); | ||||
175 | m_lastWidgetUnderMouse = nullptr; | ||||
176 | } | ||||
177 | } | ||||
178 | | ||||
179 | void PreviewItem::sendHoverEvent(QHoverEvent *event) | ||||
180 | { | ||||
181 | if (!m_widget || !m_widget->isVisible()) { | ||||
182 | return; | ||||
183 | } | ||||
184 | | ||||
185 | QPointF pos = event->pos(); | ||||
186 | | ||||
187 | QWidget *child = m_widget->childAt(pos.toPoint()); | ||||
188 | QWidget *receiver = child ? child : m_widget.data(); | ||||
189 | | ||||
190 | dispatchEnterLeave(receiver, m_lastWidgetUnderMouse, mapToGlobal(event->pos())); | ||||
191 | | ||||
192 | m_lastWidgetUnderMouse = receiver; | ||||
193 | | ||||
194 | pos = receiver->mapFrom(m_widget.data(), pos.toPoint()); | ||||
195 | | ||||
196 | QMouseEvent mouseEvent(QEvent::MouseMove, pos, receiver->mapTo(receiver->topLevelWidget(), pos.toPoint()), | ||||
197 | receiver->mapToGlobal(pos.toPoint()), | ||||
198 | Qt::NoButton, {} /*buttons*/, event->modifiers()); | ||||
199 | | ||||
200 | qApp->sendEvent(receiver, &mouseEvent); | ||||
201 | | ||||
202 | event->setAccepted(mouseEvent.isAccepted()); | ||||
203 | } | ||||
204 | | ||||
205 | // Simplified copy of QApplicationPrivate::dispatchEnterLeave | ||||
206 | void PreviewItem::dispatchEnterLeave(QWidget *enter, QWidget *leave, const QPointF &globalPosF) | ||||
207 | { | ||||
208 | if ((!enter && !leave) || (enter == leave)) { | ||||
209 | return; | ||||
210 | } | ||||
211 | | ||||
212 | QWidgetList leaveList; | ||||
213 | QWidgetList enterList; | ||||
214 | | ||||
215 | bool sameWindow = leave && enter && leave->window() == enter->window(); | ||||
216 | if (leave && !sameWindow) { | ||||
217 | auto *w = leave; | ||||
218 | do { | ||||
219 | leaveList.append(w); | ||||
220 | } while (!w->isWindow() && (w = w->parentWidget())); | ||||
221 | } | ||||
222 | if (enter && !sameWindow) { | ||||
223 | auto *w = enter; | ||||
224 | do { | ||||
225 | enterList.append(w); | ||||
226 | } while (!w->isWindow() && (w = w->parentWidget())); | ||||
227 | } | ||||
228 | if (sameWindow) { | ||||
229 | int enterDepth = 0; | ||||
230 | int leaveDepth = 0; | ||||
231 | auto *e = enter; | ||||
232 | while (!e->isWindow() && (e = e->parentWidget())) | ||||
233 | enterDepth++; | ||||
234 | auto *l = leave; | ||||
235 | while (!l->isWindow() && (l = l->parentWidget())) | ||||
236 | leaveDepth++; | ||||
237 | QWidget* wenter = enter; | ||||
238 | QWidget* wleave = leave; | ||||
239 | while (enterDepth > leaveDepth) { | ||||
240 | wenter = wenter->parentWidget(); | ||||
241 | enterDepth--; | ||||
242 | } | ||||
243 | while (leaveDepth > enterDepth) { | ||||
244 | wleave = wleave->parentWidget(); | ||||
245 | leaveDepth--; | ||||
246 | } | ||||
247 | while (!wenter->isWindow() && wenter != wleave) { | ||||
248 | wenter = wenter->parentWidget(); | ||||
249 | wleave = wleave->parentWidget(); | ||||
250 | } | ||||
251 | | ||||
252 | for (auto *w = leave; w != wleave; w = w->parentWidget()) | ||||
253 | leaveList.append(w); | ||||
254 | | ||||
255 | for (auto *w = enter; w != wenter; w = w->parentWidget()) | ||||
256 | enterList.append(w); | ||||
257 | } | ||||
258 | | ||||
259 | const QPoint globalPos = globalPosF.toPoint(); | ||||
260 | | ||||
261 | QEvent leaveEvent(QEvent::Leave); | ||||
262 | for (int i = 0; i < leaveList.size(); ++i) { | ||||
263 | auto *w = leaveList.at(i); | ||||
264 | QApplication::sendEvent(w, &leaveEvent); | ||||
265 | if (w->testAttribute(Qt::WA_Hover)) { | ||||
266 | QHoverEvent he(QEvent::HoverLeave, QPoint(-1, -1), w->mapFromGlobal(globalPos), | ||||
267 | QApplication::keyboardModifiers()); | ||||
268 | QApplication::sendEvent(w, &he); | ||||
269 | } | ||||
270 | } | ||||
271 | if (!enterList.isEmpty()) { | ||||
272 | const QPoint windowPos = qAsConst(enterList).back()->window()->mapFromGlobal(globalPos); | ||||
273 | for (auto it = enterList.crbegin(), end = enterList.crend(); it != end; ++it) { | ||||
274 | auto *w = *it; | ||||
275 | const QPointF localPos = w->mapFromGlobal(globalPos); | ||||
276 | QEnterEvent enterEvent(localPos, windowPos, globalPosF); | ||||
277 | QApplication::sendEvent(w, &enterEvent); | ||||
278 | if (w->testAttribute(Qt::WA_Hover)) { | ||||
279 | QHoverEvent he(QEvent::HoverEnter, localPos, QPoint(-1, -1), | ||||
280 | QApplication::keyboardModifiers()); | ||||
281 | QApplication::sendEvent(w, &he); | ||||
282 | } | ||||
283 | } | ||||
284 | } | ||||
285 | } | ||||
286 | | ||||
287 | void PreviewItem::geometryChanged(const QRectF &newGeometry, const QRectF &oldGeometry) | ||||
288 | { | ||||
289 | if (!m_widget || newGeometry == oldGeometry) { | ||||
290 | return; | ||||
291 | } | ||||
292 | | ||||
293 | m_widget->resize(qRound(newGeometry.width()), qRound(newGeometry.height())); | ||||
294 | } | ||||
295 | |