1 | /* |
2 | This file is part of the KDE project |
3 | SPDX-FileCopyrightText: 1999 Simon Hausmann <hausmann@kde.org> |
4 | SPDX-FileCopyrightText: 1999 David Faure <faure@kde.org> |
5 | |
6 | SPDX-License-Identifier: LGPL-2.0-or-later |
7 | */ |
8 | |
9 | #include "partmanager.h" |
10 | |
11 | #include "guiactivateevent.h" |
12 | #include "kparts_logging.h" |
13 | #include "part.h" |
14 | #include "partactivateevent.h" |
15 | |
16 | #include <QApplication> |
17 | #include <QMouseEvent> |
18 | #include <QScrollBar> |
19 | |
20 | using namespace KParts; |
21 | |
22 | namespace KParts |
23 | { |
24 | class PartManagerPrivate |
25 | { |
26 | public: |
27 | PartManagerPrivate() |
28 | { |
29 | m_activeWidget = nullptr; |
30 | m_activePart = nullptr; |
31 | m_bAllowNestedParts = false; |
32 | m_bIgnoreScrollBars = false; |
33 | m_activationButtonMask = Qt::LeftButton | Qt::MiddleButton | Qt::RightButton; |
34 | m_reason = PartManager::NoReason; |
35 | m_bIgnoreExplicitFocusRequest = false; |
36 | } |
37 | ~PartManagerPrivate() |
38 | { |
39 | } |
40 | void setReason(QEvent *ev) |
41 | { |
42 | switch (ev->type()) { |
43 | case QEvent::MouseButtonPress: |
44 | case QEvent::MouseButtonDblClick: { |
45 | // clang-format off |
46 | QMouseEvent *mev = static_cast<QMouseEvent *>(ev); |
47 | m_reason = mev->button() == Qt::LeftButton |
48 | ? PartManager::ReasonLeftClick |
49 | : (mev->button() == Qt::MiddleButton |
50 | ? PartManager::ReasonMidClick |
51 | : PartManager::ReasonRightClick); |
52 | // clang-format on |
53 | break; |
54 | } |
55 | case QEvent::FocusIn: |
56 | m_reason = static_cast<QFocusEvent *>(ev)->reason(); |
57 | break; |
58 | default: |
59 | qCWarning(KPARTSLOG) << "PartManagerPrivate::setReason got unexpected event type" << ev->type(); |
60 | break; |
61 | } |
62 | } |
63 | |
64 | bool allowExplicitFocusEvent(QEvent *ev) const |
65 | { |
66 | if (ev->type() == QEvent::FocusIn) { |
67 | QFocusEvent *fev = static_cast<QFocusEvent *>(ev); |
68 | return (!m_bIgnoreExplicitFocusRequest || fev->reason() != Qt::OtherFocusReason); |
69 | } |
70 | return true; |
71 | } |
72 | |
73 | Part *m_activePart; |
74 | QWidget *m_activeWidget; |
75 | |
76 | QList<Part *> m_parts; |
77 | |
78 | PartManager::SelectionPolicy m_policy; |
79 | |
80 | QList<const QWidget *> m_managedTopLevelWidgets; |
81 | short int m_activationButtonMask; |
82 | bool m_bIgnoreScrollBars; |
83 | bool m_bAllowNestedParts; |
84 | int m_reason; |
85 | bool m_bIgnoreExplicitFocusRequest; |
86 | }; |
87 | |
88 | } |
89 | |
90 | PartManager::PartManager(QWidget *parent) |
91 | : QObject(parent) |
92 | , d(new PartManagerPrivate) |
93 | { |
94 | qApp->installEventFilter(filterObj: this); |
95 | |
96 | d->m_policy = Direct; |
97 | |
98 | addManagedTopLevelWidget(topLevel: parent); |
99 | } |
100 | |
101 | PartManager::PartManager(QWidget *topLevel, QObject *parent) |
102 | : QObject(parent) |
103 | , d(new PartManagerPrivate) |
104 | { |
105 | qApp->installEventFilter(filterObj: this); |
106 | |
107 | d->m_policy = Direct; |
108 | |
109 | addManagedTopLevelWidget(topLevel); |
110 | } |
111 | |
112 | PartManager::~PartManager() |
113 | { |
114 | for (const QWidget *w : std::as_const(t&: d->m_managedTopLevelWidgets)) { |
115 | disconnect(sender: w, signal: &QWidget::destroyed, receiver: this, slot: &PartManager::slotManagedTopLevelWidgetDestroyed); |
116 | } |
117 | |
118 | for (Part *it : std::as_const(t&: d->m_parts)) { |
119 | it->setManager(nullptr); |
120 | } |
121 | |
122 | // core dumps ... setActivePart( 0 ); |
123 | qApp->removeEventFilter(obj: this); |
124 | } |
125 | |
126 | void PartManager::setSelectionPolicy(SelectionPolicy policy) |
127 | { |
128 | d->m_policy = policy; |
129 | } |
130 | |
131 | PartManager::SelectionPolicy PartManager::selectionPolicy() const |
132 | { |
133 | return d->m_policy; |
134 | } |
135 | |
136 | void PartManager::setAllowNestedParts(bool allow) |
137 | { |
138 | d->m_bAllowNestedParts = allow; |
139 | } |
140 | |
141 | bool PartManager::allowNestedParts() const |
142 | { |
143 | return d->m_bAllowNestedParts; |
144 | } |
145 | |
146 | void PartManager::setIgnoreScrollBars(bool ignore) |
147 | { |
148 | d->m_bIgnoreScrollBars = ignore; |
149 | } |
150 | |
151 | bool PartManager::ignoreScrollBars() const |
152 | { |
153 | return d->m_bIgnoreScrollBars; |
154 | } |
155 | |
156 | void PartManager::setActivationButtonMask(short int buttonMask) |
157 | { |
158 | d->m_activationButtonMask = buttonMask; |
159 | } |
160 | |
161 | short int PartManager::activationButtonMask() const |
162 | { |
163 | return d->m_activationButtonMask; |
164 | } |
165 | |
166 | bool PartManager::eventFilter(QObject *obj, QEvent *ev) |
167 | { |
168 | if (ev->type() != QEvent::MouseButtonPress && ev->type() != QEvent::MouseButtonDblClick && ev->type() != QEvent::FocusIn) { |
169 | return false; |
170 | } |
171 | |
172 | if (!obj->isWidgetType()) { |
173 | return false; |
174 | } |
175 | |
176 | QWidget *w = static_cast<QWidget *>(obj); |
177 | |
178 | if (((w->windowFlags().testFlag(flag: Qt::Dialog)) && w->isModal()) || (w->windowFlags().testFlag(flag: Qt::Popup)) || (w->windowFlags().testFlag(flag: Qt::Tool))) { |
179 | return false; |
180 | } |
181 | |
182 | QMouseEvent *mev = nullptr; |
183 | if (ev->type() == QEvent::MouseButtonPress || ev->type() == QEvent::MouseButtonDblClick) { |
184 | mev = static_cast<QMouseEvent *>(ev); |
185 | |
186 | qCDebug(KPARTSLOG) << "PartManager::eventFilter button:" << mev->button() << "d->m_activationButtonMask=" << d->m_activationButtonMask; |
187 | |
188 | if ((mev->button() & d->m_activationButtonMask) == 0) { |
189 | return false; // ignore this button |
190 | } |
191 | } |
192 | |
193 | Part *part; |
194 | while (w) { |
195 | QPoint pos; |
196 | |
197 | if (!d->m_managedTopLevelWidgets.contains(t: w->topLevelWidget())) { |
198 | return false; |
199 | } |
200 | |
201 | if (d->m_bIgnoreScrollBars && ::qobject_cast<QScrollBar *>(object: w)) { |
202 | return false; |
203 | } |
204 | |
205 | if (mev) { // mouse press or mouse double-click event |
206 | pos = mev->globalPosition().toPoint(); |
207 | part = findPartFromWidget(widget: w, pos); |
208 | } else { |
209 | part = findPartFromWidget(widget: w); |
210 | } |
211 | |
212 | // clang-format off |
213 | const char *evType = (ev->type() == QEvent::MouseButtonPress) ? "MouseButtonPress" |
214 | : (ev->type() == QEvent::MouseButtonDblClick) ? "MouseButtonDblClick" |
215 | : (ev->type() == QEvent::FocusIn) ? "FocusIn" : "OTHER! ERROR!" ; |
216 | // clang-format on |
217 | if (part) { // We found a part whose widget is w |
218 | if (d->m_policy == PartManager::TriState) { |
219 | if (ev->type() == QEvent::MouseButtonDblClick) { |
220 | if (part == d->m_activePart && w == d->m_activeWidget) { |
221 | return false; |
222 | } |
223 | |
224 | qCDebug(KPARTSLOG) << "PartManager::eventFilter dblclick -> setActivePart" << part; |
225 | |
226 | d->setReason(ev); |
227 | setActivePart(part, widget: w); |
228 | d->m_reason = NoReason; |
229 | return true; |
230 | } |
231 | |
232 | if ((d->m_activeWidget != w || d->m_activePart != part)) { |
233 | qCDebug(KPARTSLOG) << "Part" << part << "(non-selectable) made active because" << w->metaObject()->className() << "got event" << evType; |
234 | |
235 | d->setReason(ev); |
236 | setActivePart(part, widget: w); |
237 | d->m_reason = NoReason; |
238 | return true; |
239 | } else if (d->m_activeWidget == w && d->m_activePart == part) { |
240 | return false; |
241 | } |
242 | |
243 | return false; |
244 | } else if (part != d->m_activePart && d->allowExplicitFocusEvent(ev)) { |
245 | qCDebug(KPARTSLOG) << "Part" << part << "made active because" << w->metaObject()->className() << "got event" << evType; |
246 | |
247 | d->setReason(ev); |
248 | setActivePart(part, widget: w); |
249 | d->m_reason = NoReason; |
250 | } |
251 | |
252 | return false; |
253 | } |
254 | |
255 | w = w->parentWidget(); |
256 | |
257 | if (w && (((w->windowFlags() & Qt::Dialog) && w->isModal()) || (w->windowFlags() & Qt::Popup) || (w->windowFlags() & Qt::Tool))) { |
258 | qCDebug(KPARTSLOG) << "No part made active although" << obj->objectName() << "/" << obj->metaObject()->className() << "got event - loop aborted" ; |
259 | |
260 | return false; |
261 | } |
262 | } |
263 | |
264 | qCDebug(KPARTSLOG) << "No part made active although" << obj->objectName() << "/" << obj->metaObject()->className() << "got event - loop aborted" ; |
265 | |
266 | return false; |
267 | } |
268 | |
269 | Part *PartManager::findPartFromWidget(QWidget *widget, const QPoint &pos) |
270 | { |
271 | for (auto *p : std::as_const(t&: d->m_parts)) { |
272 | Part *part = p->hitTest(widget, globalPos: pos); |
273 | if (part && d->m_parts.contains(t: part)) { |
274 | return part; |
275 | } |
276 | } |
277 | return nullptr; |
278 | } |
279 | |
280 | Part *PartManager::findPartFromWidget(QWidget *widget) |
281 | { |
282 | for (auto *part : std::as_const(t&: d->m_parts)) { |
283 | if (widget == part->widget()) { |
284 | return part; |
285 | } |
286 | } |
287 | return nullptr; |
288 | } |
289 | |
290 | void PartManager::addPart(Part *part, bool setActive) |
291 | { |
292 | Q_ASSERT(part); |
293 | |
294 | // don't add parts more than once :) |
295 | if (d->m_parts.contains(t: part)) { |
296 | qCWarning(KPARTSLOG) << part << " already added" ; |
297 | return; |
298 | } |
299 | |
300 | d->m_parts.append(t: part); |
301 | |
302 | part->setManager(this); |
303 | |
304 | if (setActive) { |
305 | setActivePart(part); |
306 | |
307 | if (QWidget *w = part->widget()) { |
308 | // Prevent focus problems |
309 | if (w->focusPolicy() == Qt::NoFocus) { |
310 | qCWarning(KPARTSLOG) << "Part '" << part->objectName() << "' has a widget " << w->objectName() |
311 | << "with a focus policy of NoFocus. It should have at least a" |
312 | << "ClickFocus policy, for part activation to work well." ; |
313 | } |
314 | if (part->widget() && part->widget()->focusPolicy() == Qt::TabFocus) { |
315 | qCWarning(KPARTSLOG) << "Part '" << part->objectName() << "' has a widget " << w->objectName() |
316 | << "with a focus policy of TabFocus. It should have at least a" |
317 | << "ClickFocus policy, for part activation to work well." ; |
318 | } |
319 | w->setFocus(); |
320 | w->show(); |
321 | } |
322 | } |
323 | Q_EMIT partAdded(part); |
324 | } |
325 | |
326 | void PartManager::removePart(Part *part) |
327 | { |
328 | if (!d->m_parts.contains(t: part)) { |
329 | return; |
330 | } |
331 | |
332 | const int nb = d->m_parts.removeAll(t: part); |
333 | Q_ASSERT(nb == 1); |
334 | Q_UNUSED(nb); // no warning in release mode |
335 | part->setManager(nullptr); |
336 | |
337 | Q_EMIT partRemoved(part); |
338 | |
339 | if (part == d->m_activePart) { |
340 | setActivePart(part: nullptr); |
341 | } |
342 | } |
343 | |
344 | void PartManager::replacePart(Part *oldPart, Part *newPart, bool setActive) |
345 | { |
346 | // qCDebug(KPARTSLOG) << "replacePart" << oldPart->name() << "->" << newPart->name() << "setActive=" << setActive; |
347 | // This methods does exactly removePart + addPart but without calling setActivePart(0) in between |
348 | if (!d->m_parts.contains(t: oldPart)) { |
349 | qFatal(msg: "Can't remove part %s, not in KPartManager's list." , oldPart->objectName().toLocal8Bit().constData()); |
350 | return; |
351 | } |
352 | |
353 | d->m_parts.removeAll(t: oldPart); |
354 | oldPart->setManager(nullptr); |
355 | |
356 | Q_EMIT partRemoved(part: oldPart); |
357 | |
358 | addPart(part: newPart, setActive); |
359 | } |
360 | |
361 | void PartManager::setActivePart(Part *part, QWidget *widget) |
362 | { |
363 | if (part && !d->m_parts.contains(t: part)) { |
364 | qCWarning(KPARTSLOG) << "trying to activate a non-registered part!" << part->objectName(); |
365 | return; // don't allow someone call setActivePart with a part we don't know about |
366 | } |
367 | |
368 | // check whether nested parts are disallowed and activate the top parent part then, by traversing the |
369 | // tree recursively (Simon) |
370 | if (part && !d->m_bAllowNestedParts) { |
371 | QObject *parentPart = part->parent(); // ### this relies on people using KParts::Factory! |
372 | KParts::Part *parPart = ::qobject_cast<KParts::Part *>(object: parentPart); |
373 | if (parPart) { |
374 | setActivePart(part: parPart, widget: parPart->widget()); |
375 | return; |
376 | } |
377 | } |
378 | |
379 | qCDebug(KPARTSLOG) << "PartManager::setActivePart d->m_activePart=" << d->m_activePart << "<->part=" << part << "d->m_activeWidget=" << d->m_activeWidget |
380 | << "<->widget=" << widget; |
381 | |
382 | // don't activate twice |
383 | if (d->m_activePart && part && d->m_activePart == part && (!widget || d->m_activeWidget == widget)) { |
384 | return; |
385 | } |
386 | |
387 | KParts::Part *oldActivePart = d->m_activePart; |
388 | QWidget *oldActiveWidget = d->m_activeWidget; |
389 | |
390 | d->m_activePart = part; |
391 | d->m_activeWidget = widget; |
392 | |
393 | if (oldActivePart) { |
394 | KParts::Part *savedActivePart = part; |
395 | QWidget *savedActiveWidget = widget; |
396 | |
397 | PartActivateEvent ev(false, oldActivePart, oldActiveWidget); |
398 | QApplication::sendEvent(receiver: oldActivePart, event: &ev); |
399 | if (oldActiveWidget) { |
400 | disconnect(sender: oldActiveWidget, signal: &QWidget::destroyed, receiver: this, slot: &PartManager::slotWidgetDestroyed); |
401 | QApplication::sendEvent(receiver: oldActiveWidget, event: &ev); |
402 | } |
403 | |
404 | d->m_activePart = savedActivePart; |
405 | d->m_activeWidget = savedActiveWidget; |
406 | } |
407 | |
408 | if (d->m_activePart) { |
409 | if (!widget) { |
410 | d->m_activeWidget = part->widget(); |
411 | } |
412 | |
413 | PartActivateEvent ev(true, d->m_activePart, d->m_activeWidget); |
414 | QApplication::sendEvent(receiver: d->m_activePart, event: &ev); |
415 | if (d->m_activeWidget) { |
416 | connect(sender: d->m_activeWidget, signal: &QWidget::destroyed, context: this, slot: &PartManager::slotWidgetDestroyed); |
417 | QApplication::sendEvent(receiver: d->m_activeWidget, event: &ev); |
418 | } |
419 | } |
420 | // Set the new active instance |
421 | // setActiveComponent(d->m_activePart ? d->m_activePart->componentData() : KComponentData::mainComponent()); |
422 | |
423 | qCDebug(KPARTSLOG) << this << "emitting activePartChanged" << d->m_activePart; |
424 | |
425 | Q_EMIT activePartChanged(newPart: d->m_activePart); |
426 | } |
427 | |
428 | Part *PartManager::activePart() const |
429 | { |
430 | return d->m_activePart; |
431 | } |
432 | |
433 | QWidget *PartManager::activeWidget() const |
434 | { |
435 | return d->m_activeWidget; |
436 | } |
437 | |
438 | void PartManager::slotObjectDestroyed() |
439 | { |
440 | // qDebug(); |
441 | removePart(part: const_cast<Part *>(static_cast<const Part *>(sender()))); |
442 | } |
443 | |
444 | void PartManager::slotWidgetDestroyed() |
445 | { |
446 | // qDebug(); |
447 | if (static_cast<const QWidget *>(sender()) == d->m_activeWidget) { |
448 | setActivePart(part: nullptr); // do not remove the part because if the part's widget dies, then the |
449 | } |
450 | // part will delete itself anyway, invoking removePart() in its destructor |
451 | } |
452 | |
453 | const QList<Part *> PartManager::parts() const |
454 | { |
455 | return d->m_parts; |
456 | } |
457 | |
458 | void PartManager::addManagedTopLevelWidget(const QWidget *topLevel) |
459 | { |
460 | if (!topLevel->isWindow()) { |
461 | return; |
462 | } |
463 | |
464 | if (d->m_managedTopLevelWidgets.contains(t: topLevel)) { |
465 | return; |
466 | } |
467 | |
468 | d->m_managedTopLevelWidgets.append(t: topLevel); |
469 | connect(sender: topLevel, signal: &QWidget::destroyed, context: this, slot: &PartManager::slotManagedTopLevelWidgetDestroyed); |
470 | } |
471 | |
472 | void PartManager::removeManagedTopLevelWidget(const QWidget *topLevel) |
473 | { |
474 | d->m_managedTopLevelWidgets.removeAll(t: topLevel); |
475 | } |
476 | |
477 | void PartManager::slotManagedTopLevelWidgetDestroyed() |
478 | { |
479 | const QWidget *widget = static_cast<const QWidget *>(sender()); |
480 | removeManagedTopLevelWidget(topLevel: widget); |
481 | } |
482 | |
483 | int PartManager::reason() const |
484 | { |
485 | return d->m_reason; |
486 | } |
487 | |
488 | void PartManager::setIgnoreExplictFocusRequests(bool ignore) |
489 | { |
490 | d->m_bIgnoreExplicitFocusRequest = ignore; |
491 | } |
492 | |
493 | #include "moc_partmanager.cpp" |
494 | |