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
20using namespace KParts;
21
22namespace KParts
23{
24class PartManagerPrivate
25{
26public:
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
90PartManager::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
101PartManager::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
112PartManager::~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
126void PartManager::setSelectionPolicy(SelectionPolicy policy)
127{
128 d->m_policy = policy;
129}
130
131PartManager::SelectionPolicy PartManager::selectionPolicy() const
132{
133 return d->m_policy;
134}
135
136void PartManager::setAllowNestedParts(bool allow)
137{
138 d->m_bAllowNestedParts = allow;
139}
140
141bool PartManager::allowNestedParts() const
142{
143 return d->m_bAllowNestedParts;
144}
145
146void PartManager::setIgnoreScrollBars(bool ignore)
147{
148 d->m_bIgnoreScrollBars = ignore;
149}
150
151bool PartManager::ignoreScrollBars() const
152{
153 return d->m_bIgnoreScrollBars;
154}
155
156void PartManager::setActivationButtonMask(short int buttonMask)
157{
158 d->m_activationButtonMask = buttonMask;
159}
160
161short int PartManager::activationButtonMask() const
162{
163 return d->m_activationButtonMask;
164}
165
166bool 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
269Part *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
280Part *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
290void 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
326void 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
344void 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
361void 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
428Part *PartManager::activePart() const
429{
430 return d->m_activePart;
431}
432
433QWidget *PartManager::activeWidget() const
434{
435 return d->m_activeWidget;
436}
437
438void PartManager::slotObjectDestroyed()
439{
440 // qDebug();
441 removePart(part: const_cast<Part *>(static_cast<const Part *>(sender())));
442}
443
444void 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
453const QList<Part *> PartManager::parts() const
454{
455 return d->m_parts;
456}
457
458void 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
472void PartManager::removeManagedTopLevelWidget(const QWidget *topLevel)
473{
474 d->m_managedTopLevelWidgets.removeAll(t: topLevel);
475}
476
477void PartManager::slotManagedTopLevelWidgetDestroyed()
478{
479 const QWidget *widget = static_cast<const QWidget *>(sender());
480 removeManagedTopLevelWidget(topLevel: widget);
481}
482
483int PartManager::reason() const
484{
485 return d->m_reason;
486}
487
488void PartManager::setIgnoreExplictFocusRequests(bool ignore)
489{
490 d->m_bIgnoreExplicitFocusRequest = ignore;
491}
492
493#include "moc_partmanager.cpp"
494

source code of kparts/src/partmanager.cpp