File indexing completed on 2024-04-28 15:29:22

0001 /*
0002     This file is part of the KDE project
0003     SPDX-FileCopyrightText: 1999 Simon Hausmann <hausmann@kde.org>
0004     SPDX-FileCopyrightText: 1999 David Faure <faure@kde.org>
0005 
0006     SPDX-License-Identifier: LGPL-2.0-or-later
0007 */
0008 
0009 #include "partmanager.h"
0010 
0011 #include "kparts_logging.h"
0012 #include "partactivateevent.h"
0013 
0014 #if KPARTS_BUILD_DEPRECATED_SINCE(5, 72)
0015 #include "partselectevent.h"
0016 #endif
0017 
0018 #include "guiactivateevent.h"
0019 #include "part.h"
0020 
0021 #include <QApplication>
0022 #include <QMouseEvent>
0023 #include <QScrollBar>
0024 
0025 using namespace KParts;
0026 
0027 namespace KParts
0028 {
0029 class PartManagerPrivate
0030 {
0031 public:
0032     PartManagerPrivate()
0033     {
0034         m_activeWidget = nullptr;
0035         m_activePart = nullptr;
0036 
0037 #if KPARTS_BUILD_DEPRECATED_SINCE(5, 72)
0038         m_selectedPart = nullptr;
0039         m_selectedWidget = nullptr;
0040 #endif
0041 
0042         m_bAllowNestedParts = false;
0043         m_bIgnoreScrollBars = false;
0044         m_activationButtonMask = Qt::LeftButton | Qt::MiddleButton | Qt::RightButton;
0045         m_reason = PartManager::NoReason;
0046         m_bIgnoreExplicitFocusRequest = false;
0047     }
0048     ~PartManagerPrivate()
0049     {
0050     }
0051     void setReason(QEvent *ev)
0052     {
0053         switch (ev->type()) {
0054         case QEvent::MouseButtonPress:
0055         case QEvent::MouseButtonDblClick: {
0056             // clang-format off
0057             QMouseEvent *mev = static_cast<QMouseEvent *>(ev);
0058             m_reason = mev->button() == Qt::LeftButton
0059                        ? PartManager::ReasonLeftClick
0060                        : (mev->button() == Qt::MiddleButton
0061                           ? PartManager::ReasonMidClick
0062                           : PartManager::ReasonRightClick);
0063             // clang-format on
0064             break;
0065         }
0066         case QEvent::FocusIn:
0067             m_reason = static_cast<QFocusEvent *>(ev)->reason();
0068             break;
0069         default:
0070             qCWarning(KPARTSLOG) << "PartManagerPrivate::setReason got unexpected event type" << ev->type();
0071             break;
0072         }
0073     }
0074 
0075     bool allowExplicitFocusEvent(QEvent *ev) const
0076     {
0077         if (ev->type() == QEvent::FocusIn) {
0078             QFocusEvent *fev = static_cast<QFocusEvent *>(ev);
0079             return (!m_bIgnoreExplicitFocusRequest || fev->reason() != Qt::OtherFocusReason);
0080         }
0081         return true;
0082     }
0083 
0084     Part *m_activePart;
0085     QWidget *m_activeWidget;
0086 
0087     QList<Part *> m_parts;
0088 
0089     PartManager::SelectionPolicy m_policy;
0090 
0091 #if KPARTS_BUILD_DEPRECATED_SINCE(5, 72)
0092     Part *m_selectedPart;
0093     QWidget *m_selectedWidget;
0094 #endif
0095 
0096     QList<const QWidget *> m_managedTopLevelWidgets;
0097     short int m_activationButtonMask;
0098     bool m_bIgnoreScrollBars;
0099     bool m_bAllowNestedParts;
0100     int m_reason;
0101     bool m_bIgnoreExplicitFocusRequest;
0102 };
0103 
0104 }
0105 
0106 PartManager::PartManager(QWidget *parent)
0107     : QObject(parent)
0108     , d(new PartManagerPrivate)
0109 {
0110     qApp->installEventFilter(this);
0111 
0112     d->m_policy = Direct;
0113 
0114     addManagedTopLevelWidget(parent);
0115 }
0116 
0117 PartManager::PartManager(QWidget *topLevel, QObject *parent)
0118     : QObject(parent)
0119     , d(new PartManagerPrivate)
0120 {
0121     qApp->installEventFilter(this);
0122 
0123     d->m_policy = Direct;
0124 
0125     addManagedTopLevelWidget(topLevel);
0126 }
0127 
0128 PartManager::~PartManager()
0129 {
0130     for (const QWidget *w : std::as_const(d->m_managedTopLevelWidgets)) {
0131         disconnect(w, &QWidget::destroyed, this, &PartManager::slotManagedTopLevelWidgetDestroyed);
0132     }
0133 
0134     for (Part *it : std::as_const(d->m_parts)) {
0135         it->setManager(nullptr);
0136     }
0137 
0138     // core dumps ... setActivePart( 0 );
0139     qApp->removeEventFilter(this);
0140 }
0141 
0142 void PartManager::setSelectionPolicy(SelectionPolicy policy)
0143 {
0144     d->m_policy = policy;
0145 }
0146 
0147 PartManager::SelectionPolicy PartManager::selectionPolicy() const
0148 {
0149     return d->m_policy;
0150 }
0151 
0152 void PartManager::setAllowNestedParts(bool allow)
0153 {
0154     d->m_bAllowNestedParts = allow;
0155 }
0156 
0157 bool PartManager::allowNestedParts() const
0158 {
0159     return d->m_bAllowNestedParts;
0160 }
0161 
0162 void PartManager::setIgnoreScrollBars(bool ignore)
0163 {
0164     d->m_bIgnoreScrollBars = ignore;
0165 }
0166 
0167 bool PartManager::ignoreScrollBars() const
0168 {
0169     return d->m_bIgnoreScrollBars;
0170 }
0171 
0172 void PartManager::setActivationButtonMask(short int buttonMask)
0173 {
0174     d->m_activationButtonMask = buttonMask;
0175 }
0176 
0177 short int PartManager::activationButtonMask() const
0178 {
0179     return d->m_activationButtonMask;
0180 }
0181 
0182 bool PartManager::eventFilter(QObject *obj, QEvent *ev)
0183 {
0184     if (ev->type() != QEvent::MouseButtonPress && ev->type() != QEvent::MouseButtonDblClick && ev->type() != QEvent::FocusIn) {
0185         return false;
0186     }
0187 
0188     if (!obj->isWidgetType()) {
0189         return false;
0190     }
0191 
0192     QWidget *w = static_cast<QWidget *>(obj);
0193 
0194     if (((w->windowFlags().testFlag(Qt::Dialog)) && w->isModal()) || (w->windowFlags().testFlag(Qt::Popup)) || (w->windowFlags().testFlag(Qt::Tool))) {
0195         return false;
0196     }
0197 
0198     QMouseEvent *mev = nullptr;
0199     if (ev->type() == QEvent::MouseButtonPress || ev->type() == QEvent::MouseButtonDblClick) {
0200         mev = static_cast<QMouseEvent *>(ev);
0201 
0202         qCDebug(KPARTSLOG) << "PartManager::eventFilter button:" << mev->button() << "d->m_activationButtonMask=" << d->m_activationButtonMask;
0203 
0204         if ((mev->button() & d->m_activationButtonMask) == 0) {
0205             return false; // ignore this button
0206         }
0207     }
0208 
0209     Part *part;
0210     while (w) {
0211         QPoint pos;
0212 
0213         if (!d->m_managedTopLevelWidgets.contains(w->topLevelWidget())) {
0214             return false;
0215         }
0216 
0217         if (d->m_bIgnoreScrollBars && ::qobject_cast<QScrollBar *>(w)) {
0218             return false;
0219         }
0220 
0221         if (mev) { // mouse press or mouse double-click event
0222             pos = mev->globalPos();
0223             part = findPartFromWidget(w, pos);
0224         } else {
0225             part = findPartFromWidget(w);
0226         }
0227 
0228         // clang-format off
0229         const char *evType = (ev->type() == QEvent::MouseButtonPress) ? "MouseButtonPress"
0230                              : (ev->type() == QEvent::MouseButtonDblClick) ? "MouseButtonDblClick"
0231                              : (ev->type() == QEvent::FocusIn) ? "FocusIn" : "OTHER! ERROR!";
0232         // clang-format on
0233         if (part) { // We found a part whose widget is w
0234             if (d->m_policy == PartManager::TriState) {
0235                 if (ev->type() == QEvent::MouseButtonDblClick) {
0236                     if (part == d->m_activePart && w == d->m_activeWidget) {
0237                         return false;
0238                     }
0239 
0240                     qCDebug(KPARTSLOG) << "PartManager::eventFilter dblclick -> setActivePart" << part;
0241 
0242                     d->setReason(ev);
0243                     setActivePart(part, w);
0244                     d->m_reason = NoReason;
0245                     return true;
0246                 }
0247 
0248                 if (
0249 #if KPARTS_BUILD_DEPRECATED_SINCE(5, 72)
0250                     (d->m_selectedWidget != w || d->m_selectedPart != part) &&
0251 #endif
0252                     (d->m_activeWidget != w || d->m_activePart != part)) {
0253 #if KPARTS_BUILD_DEPRECATED_SINCE(5, 72)
0254                     if (part->isSelectable()) {
0255                         setSelectedPart(part, w);
0256                     } else {
0257 #endif
0258                         qCDebug(KPARTSLOG) << "Part" << part << "(non-selectable) made active because" << w->metaObject()->className() << "got event" << evType;
0259 
0260                         d->setReason(ev);
0261                         setActivePart(part, w);
0262                         d->m_reason = NoReason;
0263 #if KPARTS_BUILD_DEPRECATED_SINCE(5, 72)
0264                     }
0265 #endif
0266                     return true;
0267                 }
0268 #if KPARTS_BUILD_DEPRECATED_SINCE(5, 72)
0269                 else if (d->m_selectedWidget == w && d->m_selectedPart == part) {
0270                     qCDebug(KPARTSLOG) << "Part" << part << "made active (from selected) because" << w->metaObject()->className() << "got event" << evType;
0271 
0272                     d->setReason(ev);
0273                     setActivePart(part, w);
0274                     d->m_reason = NoReason;
0275                     return true;
0276                 }
0277 #endif
0278                 else if (d->m_activeWidget == w && d->m_activePart == part) {
0279 #if KPARTS_BUILD_DEPRECATED_SINCE(5, 72)
0280                     setSelectedPart(nullptr);
0281 #endif
0282                     return false;
0283                 }
0284 
0285                 return false;
0286             } else if (part != d->m_activePart && d->allowExplicitFocusEvent(ev)) {
0287                 qCDebug(KPARTSLOG) << "Part" << part << "made active because" << w->metaObject()->className() << "got event" << evType;
0288 
0289                 d->setReason(ev);
0290                 setActivePart(part, w);
0291                 d->m_reason = NoReason;
0292             }
0293 
0294             return false;
0295         }
0296 
0297         w = w->parentWidget();
0298 
0299         if (w && (((w->windowFlags() & Qt::Dialog) && w->isModal()) || (w->windowFlags() & Qt::Popup) || (w->windowFlags() & Qt::Tool))) {
0300             qCDebug(KPARTSLOG) << "No part made active although" << obj->objectName() << "/" << obj->metaObject()->className() << "got event - loop aborted";
0301 
0302             return false;
0303         }
0304     }
0305 
0306     qCDebug(KPARTSLOG) << "No part made active although" << obj->objectName() << "/" << obj->metaObject()->className() << "got event - loop aborted";
0307 
0308     return false;
0309 }
0310 
0311 Part *PartManager::findPartFromWidget(QWidget *widget, const QPoint &pos)
0312 {
0313     for (auto *p : std::as_const(d->m_parts)) {
0314         Part *part = p->hitTest(widget, pos);
0315         if (part && d->m_parts.contains(part)) {
0316             return part;
0317         }
0318     }
0319     return nullptr;
0320 }
0321 
0322 Part *PartManager::findPartFromWidget(QWidget *widget)
0323 {
0324     for (auto *part : std::as_const(d->m_parts)) {
0325         if (widget == part->widget()) {
0326             return part;
0327         }
0328     }
0329     return nullptr;
0330 }
0331 
0332 void PartManager::addPart(Part *part, bool setActive)
0333 {
0334     Q_ASSERT(part);
0335 
0336     // don't add parts more than once :)
0337     if (d->m_parts.contains(part)) {
0338         qCWarning(KPARTSLOG) << part << " already added";
0339         return;
0340     }
0341 
0342     d->m_parts.append(part);
0343 
0344     part->setManager(this);
0345 
0346     if (setActive) {
0347         setActivePart(part);
0348 
0349         if (QWidget *w = part->widget()) {
0350             // Prevent focus problems
0351             if (w->focusPolicy() == Qt::NoFocus) {
0352                 qCWarning(KPARTSLOG) << "Part '" << part->objectName() << "' has a widget " << w->objectName()
0353                                      << "with a focus policy of NoFocus. It should have at least a"
0354                                      << "ClickFocus policy, for part activation to work well.";
0355             }
0356             if (part->widget() && part->widget()->focusPolicy() == Qt::TabFocus) {
0357                 qCWarning(KPARTSLOG) << "Part '" << part->objectName() << "' has a widget " << w->objectName()
0358                                      << "with a focus policy of TabFocus. It should have at least a"
0359                                      << "ClickFocus policy, for part activation to work well.";
0360             }
0361             w->setFocus();
0362             w->show();
0363         }
0364     }
0365     Q_EMIT partAdded(part);
0366 }
0367 
0368 void PartManager::removePart(Part *part)
0369 {
0370     if (!d->m_parts.contains(part)) {
0371         return;
0372     }
0373 
0374     const int nb = d->m_parts.removeAll(part);
0375     Q_ASSERT(nb == 1);
0376     Q_UNUSED(nb); // no warning in release mode
0377     part->setManager(nullptr);
0378 
0379     Q_EMIT partRemoved(part);
0380 
0381     if (part == d->m_activePart) {
0382         setActivePart(nullptr);
0383     }
0384 #if KPARTS_BUILD_DEPRECATED_SINCE(5, 72)
0385     if (part == d->m_selectedPart) {
0386         setSelectedPart(nullptr);
0387     }
0388 #endif
0389 }
0390 
0391 void PartManager::replacePart(Part *oldPart, Part *newPart, bool setActive)
0392 {
0393     // qCDebug(KPARTSLOG) << "replacePart" << oldPart->name() << "->" << newPart->name() << "setActive=" << setActive;
0394     // This methods does exactly removePart + addPart but without calling setActivePart(0) in between
0395     if (!d->m_parts.contains(oldPart)) {
0396         qFatal("Can't remove part %s, not in KPartManager's list.", oldPart->objectName().toLocal8Bit().constData());
0397         return;
0398     }
0399 
0400     d->m_parts.removeAll(oldPart);
0401     oldPart->setManager(nullptr);
0402 
0403     Q_EMIT partRemoved(oldPart);
0404 
0405     addPart(newPart, setActive);
0406 }
0407 
0408 void PartManager::setActivePart(Part *part, QWidget *widget)
0409 {
0410     if (part && !d->m_parts.contains(part)) {
0411         qCWarning(KPARTSLOG) << "trying to activate a non-registered part!" << part->objectName();
0412         return; // don't allow someone call setActivePart with a part we don't know about
0413     }
0414 
0415     // check whether nested parts are disallowed and activate the top parent part then, by traversing the
0416     // tree recursively (Simon)
0417     if (part && !d->m_bAllowNestedParts) {
0418         QObject *parentPart = part->parent(); // ### this relies on people using KParts::Factory!
0419         KParts::Part *parPart = ::qobject_cast<KParts::Part *>(parentPart);
0420         if (parPart) {
0421             setActivePart(parPart, parPart->widget());
0422             return;
0423         }
0424     }
0425 
0426     qCDebug(KPARTSLOG) << "PartManager::setActivePart d->m_activePart=" << d->m_activePart << "<->part=" << part << "d->m_activeWidget=" << d->m_activeWidget
0427                        << "<->widget=" << widget;
0428 
0429     // don't activate twice
0430     if (d->m_activePart && part && d->m_activePart == part && (!widget || d->m_activeWidget == widget)) {
0431         return;
0432     }
0433 
0434     KParts::Part *oldActivePart = d->m_activePart;
0435     QWidget *oldActiveWidget = d->m_activeWidget;
0436 
0437 #if KPARTS_BUILD_DEPRECATED_SINCE(5, 72)
0438     setSelectedPart(nullptr);
0439 #endif
0440 
0441     d->m_activePart = part;
0442     d->m_activeWidget = widget;
0443 
0444     if (oldActivePart) {
0445         KParts::Part *savedActivePart = part;
0446         QWidget *savedActiveWidget = widget;
0447 
0448         PartActivateEvent ev(false, oldActivePart, oldActiveWidget);
0449         QApplication::sendEvent(oldActivePart, &ev);
0450         if (oldActiveWidget) {
0451             disconnect(oldActiveWidget, &QWidget::destroyed, this, &PartManager::slotWidgetDestroyed);
0452             QApplication::sendEvent(oldActiveWidget, &ev);
0453         }
0454 
0455         d->m_activePart = savedActivePart;
0456         d->m_activeWidget = savedActiveWidget;
0457     }
0458 
0459     if (d->m_activePart) {
0460         if (!widget) {
0461             d->m_activeWidget = part->widget();
0462         }
0463 
0464         PartActivateEvent ev(true, d->m_activePart, d->m_activeWidget);
0465         QApplication::sendEvent(d->m_activePart, &ev);
0466         if (d->m_activeWidget) {
0467             connect(d->m_activeWidget, &QWidget::destroyed, this, &PartManager::slotWidgetDestroyed);
0468             QApplication::sendEvent(d->m_activeWidget, &ev);
0469         }
0470     }
0471     // Set the new active instance
0472     // setActiveComponent(d->m_activePart ? d->m_activePart->componentData() : KComponentData::mainComponent());
0473 
0474     qCDebug(KPARTSLOG) << this << "emitting activePartChanged" << d->m_activePart;
0475 
0476     Q_EMIT activePartChanged(d->m_activePart);
0477 }
0478 
0479 Part *PartManager::activePart() const
0480 {
0481     return d->m_activePart;
0482 }
0483 
0484 QWidget *PartManager::activeWidget() const
0485 {
0486     return d->m_activeWidget;
0487 }
0488 
0489 #if KPARTS_BUILD_DEPRECATED_SINCE(5, 72)
0490 void PartManager::setSelectedPart(Part *part, QWidget *widget)
0491 {
0492     if (part == d->m_selectedPart && widget == d->m_selectedWidget) {
0493         return;
0494     }
0495 
0496     Part *oldPart = d->m_selectedPart;
0497     QWidget *oldWidget = d->m_selectedWidget;
0498 
0499     d->m_selectedPart = part;
0500     d->m_selectedWidget = widget;
0501 
0502     if (part && !widget) {
0503         d->m_selectedWidget = part->widget();
0504     }
0505 
0506     if (oldPart) {
0507         PartSelectEvent ev(false, oldPart, oldWidget);
0508         QApplication::sendEvent(oldPart, &ev);
0509         QApplication::sendEvent(oldWidget, &ev);
0510     }
0511 
0512     if (d->m_selectedPart) {
0513         PartSelectEvent ev(true, d->m_selectedPart, d->m_selectedWidget);
0514         QApplication::sendEvent(d->m_selectedPart, &ev);
0515         QApplication::sendEvent(d->m_selectedWidget, &ev);
0516     }
0517 }
0518 #endif
0519 
0520 #if KPARTS_BUILD_DEPRECATED_SINCE(5, 72)
0521 Part *PartManager::selectedPart() const
0522 {
0523     return d->m_selectedPart;
0524 }
0525 #endif
0526 
0527 #if KPARTS_BUILD_DEPRECATED_SINCE(5, 72)
0528 QWidget *PartManager::selectedWidget() const
0529 {
0530     return d->m_selectedWidget;
0531 }
0532 #endif
0533 
0534 void PartManager::slotObjectDestroyed()
0535 {
0536     // qDebug();
0537     removePart(const_cast<Part *>(static_cast<const Part *>(sender())));
0538 }
0539 
0540 void PartManager::slotWidgetDestroyed()
0541 {
0542     // qDebug();
0543     if (static_cast<const QWidget *>(sender()) == d->m_activeWidget) {
0544         setActivePart(nullptr); // do not remove the part because if the part's widget dies, then the
0545     }
0546     // part will delete itself anyway, invoking removePart() in its destructor
0547 }
0548 
0549 const QList<Part *> PartManager::parts() const
0550 {
0551     return d->m_parts;
0552 }
0553 
0554 void PartManager::addManagedTopLevelWidget(const QWidget *topLevel)
0555 {
0556     if (!topLevel->isTopLevel()) {
0557         return;
0558     }
0559 
0560     if (d->m_managedTopLevelWidgets.contains(topLevel)) {
0561         return;
0562     }
0563 
0564     d->m_managedTopLevelWidgets.append(topLevel);
0565     connect(topLevel, &QWidget::destroyed, this, &PartManager::slotManagedTopLevelWidgetDestroyed);
0566 }
0567 
0568 void PartManager::removeManagedTopLevelWidget(const QWidget *topLevel)
0569 {
0570     d->m_managedTopLevelWidgets.removeAll(topLevel);
0571 }
0572 
0573 void PartManager::slotManagedTopLevelWidgetDestroyed()
0574 {
0575     const QWidget *widget = static_cast<const QWidget *>(sender());
0576     removeManagedTopLevelWidget(widget);
0577 }
0578 
0579 int PartManager::reason() const
0580 {
0581     return d->m_reason;
0582 }
0583 
0584 void PartManager::setIgnoreExplictFocusRequests(bool ignore)
0585 {
0586     d->m_bIgnoreExplicitFocusRequest = ignore;
0587 }
0588 
0589 #include "moc_partmanager.cpp"