diff --git a/kdevplatform/shell/textdocument.h b/kdevplatform/shell/textdocument.h --- a/kdevplatform/shell/textdocument.h +++ b/kdevplatform/shell/textdocument.h @@ -86,6 +86,7 @@ void newDocumentStatus(KTextEditor::Document*); void populateContextMenu(KTextEditor::View*, QMenu*); + void unpopulateContextMenu(); void textChanged(KTextEditor::Document*); void documentUrlChanged(KTextEditor::Document*); void slotDocumentLoaded(); diff --git a/kdevplatform/shell/textdocument.cpp b/kdevplatform/shell/textdocument.cpp --- a/kdevplatform/shell/textdocument.cpp +++ b/kdevplatform/shell/textdocument.cpp @@ -86,8 +86,11 @@ ~TextDocumentPrivate() { - delete addedContextMenu; - addedContextMenu = nullptr; + // Handle the case we are being deleted while the context menu is not yet hidden. + // We want to remove all actions we added to it, especially those not owned by the document + // but by the plugins (i.e. created on-the-fly during ContextMenuExtension::populateMenu + // with ownership set to our addedContextMenu) + cleanContextMenu(); saveSessionConfig(); delete document; @@ -225,14 +228,40 @@ setStatus(document, dirty); } + void cleanContextMenu() + { + if (!addedContextMenu) { + return; + } + + if (currentContextMenu) { + const auto actions = addedContextMenu->actions(); + for (QAction* action : actions) { + currentContextMenu->removeAction(action); + } + currentContextMenu.clear(); + } + + // The addedContextMenu owns those actions created on-the-fly for the context menu + // (other than those actions only shared for the context menu, but also used elsewhere) + // and thuse deletes then on its own destruction. + // Some actions potentially could be connected to triggered-signal handlers + // using Qt::QueuedConnection (at least SwitchToBuddyPlugin does so currently). + // Deleting them here also would also delete the connection before the handler is triggered. + // So we delay the menu's and thus their destruction to the next eventloop by default. + addedContextMenu->deleteLater(); + addedContextMenu = nullptr; + } + TextDocument * const q; QPointer document; IDocument::DocumentState state = IDocument::Clean; QString encoding; bool loaded = false; // we want to remove the added stuff when the menu hides QMenu* addedContextMenu = nullptr; + QPointer currentContextMenu; }; class TextViewPrivate @@ -670,15 +699,25 @@ notifyContentChanged(); } +void KDevelop::TextDocument::unpopulateContextMenu() +{ + auto* menu = qobject_cast(sender()); + + disconnect(menu, &QMenu::aboutToHide, this, &TextDocument::unpopulateContextMenu); + + d->cleanContextMenu(); +} + void KDevelop::TextDocument::populateContextMenu( KTextEditor::View* v, QMenu* menu ) { if (d->addedContextMenu) { - foreach ( QAction* action, d->addedContextMenu->actions() ) { - menu->removeAction(action); - } - delete d->addedContextMenu; + qCWarning(SHELL) << "populateContextMenu() called while we still handled another menu."; + d->cleanContextMenu(); } + d->currentContextMenu = menu; + connect(menu, &QMenu::aboutToHide, this, &TextDocument::unpopulateContextMenu); + d->addedContextMenu = new QMenu(); EditorContext c(v, v->cursorPosition());