diff --git a/src/radial/radialplugin.cpp b/src/radial/radialplugin.cpp index 2bca716..0f63aae 100644 --- a/src/radial/radialplugin.cpp +++ b/src/radial/radialplugin.cpp @@ -21,6 +21,7 @@ #include #include #include +#include #include #include #include @@ -254,29 +255,45 @@ bool RadialView::triggerOllieVerb(const QString &actionId) const } const QString verb = actionId.mid(QStringLiteral("ollie:").size()); - // The selection-prompt verbs share one method with an argument; the rest are - // zero-argument methods matching the Q_INVOKABLE names on the Ollie view. - if (verb == QLatin1String("fix")) { - QMetaObject::invokeMethod(view, "submitSelectionPrompt", - Q_ARG(QString, QStringLiteral("Fix"))); - } else if (verb == QLatin1String("refactor")) { - QMetaObject::invokeMethod(view, "submitSelectionPrompt", - Q_ARG(QString, QStringLiteral("Refactor"))); - } else if (verb == QLatin1String("test")) { - QMetaObject::invokeMethod(view, "submitSelectionPrompt", - Q_ARG(QString, QStringLiteral("Write unit tests for"))); - } else if (verb == QLatin1String("doc")) { - QMetaObject::invokeMethod(view, "submitSelectionPrompt", - Q_ARG(QString, QStringLiteral("Add documentation comments to"))); - } else if (verb == QLatin1String("ask")) { - QMetaObject::invokeMethod(view, "askAboutFile"); - } else if (verb == QLatin1String("explain")) { - QMetaObject::invokeMethod(view, "explainSelection"); - } else if (verb == QLatin1String("verbatim")) { - QMetaObject::invokeMethod(view, "sendVerbatim"); - } else if (verb == QLatin1String("start")) { - QMetaObject::invokeMethod(view, "startSession"); - } + // Defer the invocation to the next event-loop turn. We are called from + // RadialMenu::finishActivated(), which emits activatedId() while the radial + // (a Qt::Popup) is still tearing down its popup grab/focus. During that + // window MainWindow::activeView() can be transiently null and the editor's + // selection is not reliably readable — the Ollie actions read both live, so + // invoking synchronously here makes them intermittently no-op. Running after + // the popup has fully closed restores the active view and selection first. + // (The Kate-action path does not need this: QAction::trigger() does not + // depend on activeView() being valid at that instant.) Guard the view with + // QPointer in case the plugin is unloaded before the deferred call runs. + QPointer safeView = view; + auto invoke = [safeView, verb]() { + QObject *v = safeView; + if (!v) { + return; + } + if (verb == QLatin1String("fix")) { + QMetaObject::invokeMethod(v, "submitSelectionPrompt", + Q_ARG(QString, QStringLiteral("Fix"))); + } else if (verb == QLatin1String("refactor")) { + QMetaObject::invokeMethod(v, "submitSelectionPrompt", + Q_ARG(QString, QStringLiteral("Refactor"))); + } else if (verb == QLatin1String("test")) { + QMetaObject::invokeMethod(v, "submitSelectionPrompt", + Q_ARG(QString, QStringLiteral("Write unit tests for"))); + } else if (verb == QLatin1String("doc")) { + QMetaObject::invokeMethod(v, "submitSelectionPrompt", + Q_ARG(QString, QStringLiteral("Add documentation comments to"))); + } else if (verb == QLatin1String("ask")) { + QMetaObject::invokeMethod(v, "askAboutFile"); + } else if (verb == QLatin1String("explain")) { + QMetaObject::invokeMethod(v, "explainSelection"); + } else if (verb == QLatin1String("verbatim")) { + QMetaObject::invokeMethod(v, "sendVerbatim"); + } else if (verb == QLatin1String("start")) { + QMetaObject::invokeMethod(v, "startSession"); + } + }; + QTimer::singleShot(0, m_mainWindow->window(), invoke); return true; }