| 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 | |