ProjectExplorer: Refactor task handlers setup

The new approach no longer assumes a fixed list, making dynamic creation
of ITaskHandler subclasses possible.

Change-Id: I86c53eaee5fc30844ce9f2d9399d5319bbf5658b
Reviewed-by: hjk <hjk@qt.io>
This commit is contained in:
Christian Kandeler
2025-06-16 13:48:16 +02:00
parent d7bed16f91
commit dfbd0f56e3
10 changed files with 217 additions and 124 deletions

View File

@@ -24,7 +24,7 @@ void SearchTaskHandler::handle(const ProjectExplorer::Task &task)
emit search(QUrl("https://www.google.com/search?q=" + task.summary));
}
QAction *SearchTaskHandler::createAction(QObject *parent) const
QAction *SearchTaskHandler::createAction() const
{
return new QAction(Tr::tr("Get Help Online"), parent);
return new QAction(Tr::tr("Get Help Online"));
}

View File

@@ -17,12 +17,15 @@ class SearchTaskHandler : public ProjectExplorer::ITaskHandler
Q_OBJECT
public:
bool canHandle(const ProjectExplorer::Task &task) const override;
void handle(const ProjectExplorer::Task &task) override;
QAction *createAction(QObject *parent) const override;
SearchTaskHandler() : ProjectExplorer::ITaskHandler(createAction()) {}
signals:
void search(const QUrl &url);
private:
bool canHandle(const ProjectExplorer::Task &task) const override;
void handle(const ProjectExplorer::Task &task) override;
QAction *createAction() const;
};
} // namespace Internal

View File

@@ -73,7 +73,6 @@
#include "simpleprojectwizard.h"
#include "target.h"
#include "taskfile.h"
#include "taskhandlers.h"
#include "taskhub.h"
#include "toolchainmanager.h"
#include "toolchainoptionspage.h"
@@ -823,8 +822,6 @@ Result<> ProjectExplorerPlugin::initialize(const QStringList &arguments)
setupSanitizerOutputParser();
setupTaskHandlers();
setupJsonWizardPages();
setupJsonWizardFileGenerator();
setupJsonWizardScannerGenerator();

View File

@@ -15,9 +15,13 @@
namespace ProjectExplorer {
namespace Internal {
ShowOutputTaskHandler::ShowOutputTaskHandler(Core::IOutputPane *window, const QString &text,
const QString &tooltip, const QString &shortcut)
: m_window(window), m_text(text), m_tooltip(tooltip), m_shortcut(shortcut)
ShowOutputTaskHandler::ShowOutputTaskHandler(
Core::IOutputPane *window, const QString &text, const QString &tooltip, const QString &shortcut)
: ITaskHandler(createAction(text, tooltip, shortcut))
, m_window(window)
, m_text(text)
, m_tooltip(tooltip)
, m_shortcut(shortcut)
{
QTC_CHECK(m_window);
QTC_CHECK(!m_text.isEmpty());
@@ -44,13 +48,14 @@ void ShowOutputTaskHandler::handle(const Task &task)
}
}
QAction *ShowOutputTaskHandler::createAction(QObject *parent) const
QAction *ShowOutputTaskHandler::createAction(const QString &text, const QString &tooltip,
const QString &shortcut)
{
QAction * const outputAction = new QAction(m_text, parent);
if (!m_tooltip.isEmpty())
outputAction->setToolTip(m_tooltip);
if (!m_shortcut.isEmpty())
outputAction->setShortcut(QKeySequence(m_shortcut));
QAction * const outputAction = new QAction(text);
if (!tooltip.isEmpty())
outputAction->setToolTip(tooltip);
if (!shortcut.isEmpty())
outputAction->setShortcut(QKeySequence(shortcut));
outputAction->setShortcutContext(Qt::WidgetWithChildrenShortcut);
return outputAction;
}

View File

@@ -18,11 +18,12 @@ public:
explicit ShowOutputTaskHandler(Core::IOutputPane *window, const QString &text,
const QString &tooltip, const QString &shortcut);
private:
bool canHandle(const Task &) const override;
void handle(const Task &task) override;
QAction *createAction(QObject *parent) const override;
static QAction *createAction(const QString &text, const QString &tooltip,
const QString &shortcut);
private:
Core::IOutputPane * const m_window;
const QString m_text;
const QString m_tooltip;

View File

@@ -226,11 +226,11 @@ void StopMonitoringHandler::handle(const ProjectExplorer::Task &task)
TaskFile::stopMonitoring();
}
QAction *StopMonitoringHandler::createAction(QObject *parent) const
QAction *StopMonitoringHandler::createAction() const
{
const QString text = Tr::tr("Stop Monitoring");
const QString toolTip = Tr::tr("Stop monitoring task files.");
auto stopMonitoringAction = new QAction(text, parent);
auto stopMonitoringAction = new QAction(text);
stopMonitoringAction->setToolTip(toolTip);
return stopMonitoringAction;
}

View File

@@ -13,9 +13,12 @@ namespace Internal {
class StopMonitoringHandler : public ITaskHandler
{
public:
StopMonitoringHandler() : ITaskHandler(createAction()) {}
private:
bool canHandle(const ProjectExplorer::Task &) const override;
void handle(const ProjectExplorer::Task &) override;
QAction *createAction(QObject *parent) const override;
QAction *createAction() const;
};
class TaskFile : public Core::IDocument

View File

@@ -7,29 +7,105 @@
#include "projectexplorertr.h"
#include "taskhub.h"
#include <coreplugin/actionmanager/actionmanager.h>
#include <coreplugin/actionmanager/command.h>
#include <coreplugin/coreconstants.h>
#include <coreplugin/customlanguagemodels.h>
#include <coreplugin/editormanager/editormanager.h>
#include <coreplugin/icontext.h>
#include <coreplugin/icore.h>
#include <coreplugin/iversioncontrol.h>
#include <coreplugin/messagemanager.h>
#include <coreplugin/vcsmanager.h>
#include <utils/algorithm.h>
#include <utils/qtcprocess.h>
#include <utils/stringutils.h>
#include <QHash>
#include <QTimer>
using namespace Core;
using namespace Utils;
namespace ProjectExplorer::Internal {
namespace ProjectExplorer {
namespace {
static QObject *g_actionParent = nullptr;
static Core::Context g_cmdContext;
static Internal::RegisterHandlerAction g_onCreateAction;
static Internal::GetHandlerTasks g_getTasks;
static QList<ITaskHandler *> g_toRegister;
static QList<ITaskHandler *> g_taskHandlers;
} // namespace
ITaskHandler::ITaskHandler(QAction *action, const Id &actionId, bool isMultiHandler)
: m_action(action), m_actionId(actionId), m_isMultiHandler(isMultiHandler)
{
if (g_actionParent)
registerHandler();
else
g_toRegister << this;
}
ITaskHandler::~ITaskHandler()
{
g_toRegister.removeOne(this);
deregisterHandler();
delete m_action;
}
void ITaskHandler::handle(const Task &task)
{
QTC_ASSERT(m_isMultiHandler, return);
handle(Tasks{task});
}
void ITaskHandler::handle(const Tasks &tasks)
{
QTC_ASSERT(canHandle(tasks), return);
QTC_ASSERT(!m_isMultiHandler, return);
handle(tasks.first());
}
bool ITaskHandler::canHandle(const Tasks &tasks) const
{
if (tasks.isEmpty())
return false;
if (m_isMultiHandler)
return true;
if (tasks.size() > 1)
return false;
return canHandle(tasks.first());
}
void ITaskHandler::registerHandler()
{
g_taskHandlers.append(this);
m_action->setParent(g_actionParent);
QAction *action = m_action;
connect(m_action, &QAction::triggered, this, [this] { handle(g_getTasks()); });
if (m_actionId.isValid()) {
Core::Command *cmd =
Core::ActionManager::registerAction(m_action, m_actionId, g_cmdContext, true);
action = cmd->action();
}
g_onCreateAction(action);
}
void ITaskHandler::deregisterHandler()
{
g_taskHandlers.removeOne(this);
}
namespace Internal {
namespace {
class ConfigTaskHandler : public ITaskHandler
{
public:
ConfigTaskHandler(const Task &pattern, Id page)
: m_pattern(pattern)
: ITaskHandler(createAction())
, m_pattern(pattern)
, m_targetPage(page)
{}
@@ -45,9 +121,9 @@ private:
ICore::showOptionsDialog(m_targetPage);
}
QAction *createAction(QObject *parent) const override
QAction *createAction() const
{
auto action = new QAction(ICore::msgShowOptionsDialog(), parent);
auto action = new QAction(ICore::msgShowOptionsDialog());
action->setToolTip(ICore::msgShowOptionsDialogToolTip());
return action;
}
@@ -60,7 +136,7 @@ private:
class CopyTaskHandler : public ITaskHandler
{
public:
CopyTaskHandler() : ITaskHandler(true) {}
CopyTaskHandler() : ITaskHandler(new Action, Core::Constants::COPY, true) {}
private:
void handle(const Tasks &tasks) override
@@ -85,15 +161,12 @@ private:
}
setClipboardAndSelection(lines.join('\n'));
}
Id actionManagerId() const override { return Id(Core::Constants::COPY); }
QAction *createAction(QObject *parent) const override { return new QAction(parent); }
};
class RemoveTaskHandler : public ITaskHandler
{
public:
RemoveTaskHandler() : ITaskHandler(true) {}
RemoveTaskHandler() : ITaskHandler(createAction(), {}, true) {}
private:
void handle(const Tasks &tasks) override
@@ -102,10 +175,10 @@ private:
TaskHub::removeTask(task);
}
QAction *createAction(QObject *parent) const override
QAction *createAction() const
{
QAction *removeAction = new QAction(
Tr::tr("Remove", "Name of the action triggering the removetaskhandler"), parent);
Tr::tr("Remove", "Name of the action triggering the removetaskhandler"));
removeAction->setToolTip(Tr::tr("Remove task from the task list."));
removeAction->setShortcuts({QKeySequence::Delete, QKeySequence::Backspace});
removeAction->setShortcutContext(Qt::WidgetWithChildrenShortcut);
@@ -115,6 +188,10 @@ private:
class ShowInEditorTaskHandler : public ITaskHandler
{
public:
ShowInEditorTaskHandler() : ITaskHandler(createAction()) {}
private:
bool isDefaultHandler() const override { return true; }
bool canHandle(const Task &task) const override
@@ -132,9 +209,9 @@ class ShowInEditorTaskHandler : public ITaskHandler
{task.file, task.movedLine, column}, {}, EditorManager::SwitchSplitIfAlreadyVisible);
}
QAction *createAction(QObject *parent ) const override
QAction *createAction() const
{
QAction *showAction = new QAction(Tr::tr("Show in Editor"), parent);
QAction *showAction = new QAction(Tr::tr("Show in Editor"));
showAction->setToolTip(Tr::tr("Show task location in an editor."));
showAction->setShortcut(QKeySequence(Qt::Key_Return));
showAction->setShortcutContext(Qt::WidgetWithChildrenShortcut);
@@ -144,6 +221,10 @@ class ShowInEditorTaskHandler : public ITaskHandler
class VcsAnnotateTaskHandler : public ITaskHandler
{
public:
VcsAnnotateTaskHandler() : ITaskHandler(createAction()) {}
private:
bool canHandle(const Task &task) const override
{
QFileInfo fi(task.file.toFileInfo());
@@ -163,18 +244,21 @@ class VcsAnnotateTaskHandler : public ITaskHandler
vc->vcsAnnotate(task.file.absoluteFilePath(), task.movedLine);
}
QAction *createAction(QObject *parent) const override
QAction *createAction() const
{
QAction *vcsannotateAction = new QAction(Tr::tr("&Annotate"), parent);
QAction *vcsannotateAction = new QAction(Tr::tr("&Annotate"));
vcsannotateAction->setToolTip(Tr::tr("Annotate using version control system."));
return vcsannotateAction;
}
};
// FIXME: There should be one handler per LLM, but the ITaskHandler infrastructure is
// currently static. Alternatively, we could somehow multiplex from here, perhaps via a submenu.
// FIXME: There should be one handler per LLM
class ExplainWithAiHandler : public ITaskHandler
{
public:
ExplainWithAiHandler() : ITaskHandler(createAction()) {}
private:
bool canHandle(const Task &task) const override
{
Q_UNUSED(task)
@@ -229,9 +313,9 @@ class ExplainWithAiHandler : public ITaskHandler
process->start();
}
QAction *createAction(QObject *parent) const override
QAction *createAction() const
{
const auto action = new QAction(Tr::tr("Get help from AI"), parent);
const auto action = new QAction(Tr::tr("Get help from AI"));
action->setToolTip(Tr::tr("Ask an AI to help with this issue."));
return action;
}
@@ -239,8 +323,17 @@ class ExplainWithAiHandler : public ITaskHandler
} // namespace
void setupTaskHandlers()
void setupTaskHandlers(
QObject *actionParent,
const Core::Context &cmdContext,
const RegisterHandlerAction &onCreateAction,
const GetHandlerTasks &getTasks)
{
g_actionParent = actionParent;
g_cmdContext = cmdContext;
g_onCreateAction = onCreateAction;
g_getTasks = getTasks;
static const ConfigTaskHandler
configTaskHandler(Task::compilerMissingTask(), Constants::KITS_SETTINGS_PAGE_ID);
static const CopyTaskHandler copyTaskHandler;
@@ -248,6 +341,35 @@ void setupTaskHandlers()
static const ShowInEditorTaskHandler showInEditorTaskHandler;
static const VcsAnnotateTaskHandler vcsAnnotateTaskHandler;
static const ExplainWithAiHandler explainWithAiHandler;
registerQueuedTaskHandlers();
}
} // namespace ProjectExplorer::Internal
ITaskHandler *taskHandlerForAction(QAction *action)
{
return Utils::findOrDefault(g_taskHandlers,
[action](ITaskHandler *h) { return h->action() == action; });
}
void updateTaskHandlerActionsState()
{
const Tasks tasks = g_getTasks();
for (ITaskHandler * const h : g_taskHandlers)
h->action()->setEnabled(h->canHandle(tasks));
}
ITaskHandler *defaultTaskHandler()
{
return Utils::findOrDefault(g_taskHandlers,
[](ITaskHandler *h) { return h->isDefaultHandler(); });
}
void registerQueuedTaskHandlers()
{
for (ITaskHandler * const h : std::as_const(g_toRegister))
h->registerHandler();
g_toRegister.clear();
}
} // namespace Internal
} // namespace ProjectExplorer

View File

@@ -9,33 +9,58 @@
#include <utils/id.h>
#include <QObject>
#include <QPointer>
#include <QString>
#include <functional>
QT_BEGIN_NAMESPACE
class QAction;
QT_END_NAMESPACE
namespace Core { class Context; }
namespace ProjectExplorer {
namespace Internal { void registerQueuedTaskHandlers(); }
class PROJECTEXPLORER_EXPORT ITaskHandler : public QObject
{
public:
explicit ITaskHandler(bool isMultiHandler = false);
explicit ITaskHandler(QAction *action, const Utils::Id &actionId = {},
bool isMultiHandler = false);
~ITaskHandler() override;
virtual bool isDefaultHandler() const { return false; }
virtual bool canHandle(const Task &) const { return m_isMultiHandler; }
virtual void handle(const Task &); // Non-multi-handlers should implement this.
virtual void handle(const Task &task); // Non-multi-handlers should implement this.
virtual void handle(const Tasks &tasks); // Multi-handlers should implement this.
virtual Utils::Id actionManagerId() const { return Utils::Id(); }
virtual QAction *createAction(QObject *parent) const = 0;
bool canHandle(const Tasks &tasks) const;
QAction *action() const { return m_action; }
private:
friend void Internal::registerQueuedTaskHandlers();
void registerHandler();
void deregisterHandler();
const QPointer<QAction> m_action;
const Utils::Id m_actionId;
const bool m_isMultiHandler;
};
namespace Internal { void setupTaskHandlers(); }
namespace Internal {
using RegisterHandlerAction = std::function<void(QAction *)>;
using GetHandlerTasks = std::function<Tasks()>;
void setupTaskHandlers(
QObject *actionParent,
const Core::Context &cmdContext,
const RegisterHandlerAction &onCreateAction,
const GetHandlerTasks &getTasks);
ITaskHandler *taskHandlerForAction(QAction *action);
ITaskHandler *defaultTaskHandler();
void updateTaskHandlerActionsState();
} // namespace Internal
} // namespace ProjectExplorer

View File

@@ -11,8 +11,8 @@
#include "taskhub.h"
#include "taskmodel.h"
#include <coreplugin/actionmanager/actionmanager.h>
#include <coreplugin/actionmanager/command.h>
#include <coreplugin/coreconstants.h>
#include <coreplugin/editormanager/editormanager.h>
#include <coreplugin/find/itemviewfind.h>
#include <coreplugin/icontext.h>
@@ -49,42 +49,6 @@ const char SESSION_FILTER_WARNINGS[] = "TaskWindow.IncludeWarnings";
namespace ProjectExplorer {
static QList<ITaskHandler *> g_taskHandlers;
ITaskHandler::ITaskHandler(bool isMultiHandler) : m_isMultiHandler(isMultiHandler)
{
g_taskHandlers.append(this);
}
ITaskHandler::~ITaskHandler()
{
g_taskHandlers.removeOne(this);
}
void ITaskHandler::handle(const Task &task)
{
QTC_ASSERT(m_isMultiHandler, return);
handle(Tasks{task});
}
void ITaskHandler::handle(const Tasks &tasks)
{
QTC_ASSERT(canHandle(tasks), return);
QTC_ASSERT(!m_isMultiHandler, return);
handle(tasks.first());
}
bool ITaskHandler::canHandle(const Tasks &tasks) const
{
if (tasks.isEmpty())
return false;
if (m_isMultiHandler)
return true;
if (tasks.size() > 1)
return false;
return canHandle(tasks.first());
}
namespace Internal {
class TaskView : public TreeView
@@ -131,18 +95,10 @@ private:
class TaskWindowPrivate
{
public:
ITaskHandler *handler(QAction *action)
{
ITaskHandler *handler = m_actionToHandlerMap.value(action, nullptr);
return g_taskHandlers.contains(handler) ? handler : nullptr;
}
Internal::TaskModel *m_model;
Internal::TaskFilterModel *m_filter;
TaskView m_treeView;
const Core::Context m_taskWindowContext{Core::Context(Core::Constants::C_PROBLEM_PANE)};
QHash<QAction *, ITaskHandler *> m_actionToHandlerMap;
ITaskHandler *m_defaultHandler = nullptr;
QToolButton *m_filterWarningsButton;
QToolButton *m_categoriesButton;
QToolButton *m_externalButton = nullptr;
@@ -195,14 +151,7 @@ TaskWindow::TaskWindow() : d(std::make_unique<TaskWindowPrivate>())
connect(&d->m_treeView, &QAbstractItemView::activated,
this, &TaskWindow::triggerDefaultHandler);
connect(d->m_treeView.selectionModel(), &QItemSelectionModel::selectionChanged,
this, [this] {
const Tasks tasks = d->m_filter->tasks(d->m_treeView.selectionModel()->selectedIndexes());
for (auto it = d->m_actionToHandlerMap.cbegin(); it != d->m_actionToHandlerMap.cend(); ++it) {
ITaskHandler * const h = it.value();
QTC_ASSERT(g_taskHandlers.contains(h), continue);
it.key()->setEnabled(h && h->canHandle(tasks));
}
});
this, [] { updateTaskHandlerActionsState(); });
d->m_treeView.setContextMenuPolicy(Qt::ActionsContextMenu);
@@ -278,28 +227,15 @@ void TaskWindow::delayedInitialization()
alreadyDone = true;
for (ITaskHandler *h : std::as_const(g_taskHandlers)) {
if (h->isDefaultHandler() && !d->m_defaultHandler)
d->m_defaultHandler = h;
QAction *action = h->createAction(this);
const auto registerTaskHandlerAction = [this](QAction *action) {
action->setParent(this);
action->setEnabled(false);
QTC_ASSERT(action, continue);
d->m_actionToHandlerMap.insert(action, h);
connect(action, &QAction::triggered, this, [this, action] {
ITaskHandler *h = d->handler(action);
if (h)
h->handle(d->m_filter->tasks(d->m_treeView.selectionModel()->selectedIndexes()));
});
Id id = h->actionManagerId();
if (id.isValid()) {
Core::Command *cmd =
Core::ActionManager::registerAction(action, id, d->m_taskWindowContext, true);
action = cmd->action();
}
d->m_treeView.addAction(action);
}
};
const auto getTasksForHandler = [this] {
return d->m_filter->tasks(d->m_treeView.selectionModel()->selectedIndexes());
};
setupTaskHandlers(this, d->m_taskWindowContext, registerTaskHandlerAction, getTasksForHandler);
}
QList<QWidget*> TaskWindow::toolBarWidgets() const
@@ -429,7 +365,8 @@ void TaskWindow::openTask(const Task &task)
void TaskWindow::triggerDefaultHandler(const QModelIndex &index)
{
if (!index.isValid() || !d->m_defaultHandler)
ITaskHandler * const defaultHandler = defaultTaskHandler();
if (!index.isValid() || !defaultHandler)
return;
QModelIndex taskIndex = index;
@@ -450,8 +387,8 @@ void TaskWindow::triggerDefaultHandler(const QModelIndex &index)
}
}
if (d->m_defaultHandler->canHandle(task)) {
d->m_defaultHandler->handle(task);
if (defaultHandler->canHandle(task)) {
defaultHandler->handle(task);
} else {
if (!task.file.exists())
d->m_model->setFileNotFound(taskIndex, true);