File indexing completed on 2024-05-19 03:59:08

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