Terminal: Make shortcuts configurable

Fixes: QTCREATORBUG-28940
Change-Id: I1afe114c357243a8fa4f101b5eac80a766dc87b9
Reviewed-by: Cristian Adam <cristian.adam@qt.io>
This commit is contained in:
Marcus Tillmanns
2023-03-23 11:04:54 +01:00
parent f00a4ef46b
commit a485f18a94
7 changed files with 298 additions and 150 deletions

View File

@@ -10,6 +10,7 @@ add_qtc_plugin(Terminal
shellintegration.cpp shellintegration.h
shellmodel.cpp shellmodel.h
terminal.qrc
terminalcommands.cpp terminalcommands.h
terminalpane.cpp terminalpane.h
terminalplugin.cpp terminalplugin.h
terminalprocessimpl.cpp terminalprocessimpl.h

View File

@@ -0,0 +1,132 @@
// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#include "terminalcommands.h"
#include <coreplugin/actionmanager/actionmanager.h>
#include <coreplugin/coreconstants.h>
#include <utils/hostosinfo.h>
//#include <coreplugin/context.h>
using namespace Core;
using namespace Utils;
namespace Terminal {
constexpr char COPY[] = "Terminal.Copy";
constexpr char PASTE[] = "Terminal.Paste";
constexpr char CLEARSELECTION[] = "Terminal.ClearSelection";
constexpr char NEWTERMINAL[] = "Terminal.NewTerminal";
constexpr char CLOSETERMINAL[] = "Terminal.CloseTerminal";
constexpr char NEXTTERMINAL[] = "Terminal.NextTerminal";
constexpr char PREVTERMINAL[] = "Terminal.PrevTerminal";
constexpr char MINMAX[] = "Terminal.MinMax";
TerminalCommands &TerminalCommands::instance()
{
static TerminalCommands instance;
return instance;
}
TerminalCommands::TerminalCommands() {}
void TerminalCommands::init(const Core::Context &context)
{
initWidgetActions(context);
initPaneActions(context);
initGlobalCommands();
}
void TerminalCommands::initWidgetActions(const Core::Context &context)
{
Command *command = ActionManager::instance()->registerAction(&m_widgetActions.copy,
COPY,
context);
command->setDefaultKeySequences(
{QKeySequence(HostOsInfo::isMacHost() ? QLatin1String("Ctrl+C")
: QLatin1String("Ctrl+Shift+C")),
QKeySequence(Qt::Key_Return)});
m_commands.push_back(command);
command = ActionManager::instance()->registerAction(&m_widgetActions.paste, PASTE, context);
command->setDefaultKeySequence(QKeySequence(
HostOsInfo::isMacHost() ? QLatin1String("Ctrl+V") : QLatin1String("Ctrl+Shift+V")));
m_commands.push_back(command);
command = ActionManager::instance()->registerAction(&m_widgetActions.clearSelection,
CLEARSELECTION);
command->setDefaultKeySequence(QKeySequence("Esc"));
m_commands.push_back(command);
}
void TerminalCommands::initPaneActions(const Core::Context &context)
{
Command *command = ActionManager::instance()->registerAction(&m_paneActions.newTerminal,
NEWTERMINAL,
context);
command->setDefaultKeySequence(QKeySequence(
HostOsInfo::isMacHost() ? QLatin1String("Ctrl+T") : QLatin1String("Ctrl+Shift+T")));
m_commands.push_back(command);
command = ActionManager::instance()->registerAction(&m_paneActions.closeTerminal,
CLOSETERMINAL,
context);
command->setDefaultKeySequence(QKeySequence(
HostOsInfo::isMacHost() ? QLatin1String("Ctrl+W") : QLatin1String("Ctrl+Shift+W")));
m_commands.push_back(command);
command = ActionManager::instance()->registerAction(&m_paneActions.nextTerminal,
NEXTTERMINAL,
context);
command->setDefaultKeySequences(
{QKeySequence("ALT+TAB"),
QKeySequence(HostOsInfo::isMacHost() ? QLatin1String("Ctrl+Shift+[")
: QLatin1String("Ctrl+PgUp"))});
m_commands.push_back(command);
command = ActionManager::instance()->registerAction(&m_paneActions.prevTerminal,
PREVTERMINAL,
context);
command->setDefaultKeySequences(
{QKeySequence("ALT+SHIFT+TAB"),
QKeySequence(HostOsInfo::isMacHost() ? QLatin1String("Ctrl+Shift+]")
: QLatin1String("Ctrl+PgDown"))});
m_commands.push_back(command);
command = ActionManager::instance()->registerAction(&m_paneActions.minMax, MINMAX, context);
command->setDefaultKeySequence(QKeySequence(
HostOsInfo::isMacHost() ? QLatin1String("Ctrl+Return") : QLatin1String("Alt+Return")));
m_commands.push_back(command);
}
void TerminalCommands::initGlobalCommands()
{
// Global commands we still want to allow
m_commands.push_back(ActionManager::command(Constants::ZOOM_IN));
m_commands.push_back(ActionManager::command(Constants::ZOOM_OUT));
m_commands.push_back(ActionManager::command(Constants::EXIT));
m_commands.push_back(ActionManager::command(Constants::OPTIONS));
}
bool TerminalCommands::triggerAction(QKeyEvent *event)
{
for (const auto &command : TerminalCommands::instance().m_commands) {
if (!command->action()->isEnabled())
continue;
for (const auto &shortcut : command->keySequences()) {
const auto result = shortcut.matches(QKeySequence(event->keyCombination()));
if (result == QKeySequence::ExactMatch) {
command->action()->trigger();
return true;
}
}
}
return false;
}
} // namespace Terminal

View File

@@ -0,0 +1,59 @@
// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#pragma once
#include "terminaltr.h"
#include <QAction>
#include <QKeyEvent>
namespace Core {
class Command;
class Context;
}
namespace Terminal {
struct WidgetActions
{
QAction copy{Tr::tr("Copy")};
QAction paste{Tr::tr("Paste")};
QAction clearSelection{Tr::tr("Clear Selection")};
};
struct PaneActions
{
QAction newTerminal{Tr::tr("New Terminal")};
QAction closeTerminal{Tr::tr("Close Terminal")};
QAction nextTerminal{Tr::tr("Next Terminal")};
QAction prevTerminal{Tr::tr("Previous Terminal")};
QAction minMax{Tr::tr("Minimize/Maximize Terminal")};
};
class TerminalCommands
{
public:
TerminalCommands();
void init(const Core::Context &context);
static TerminalCommands &instance();
static WidgetActions &widgetActions() { return instance().m_widgetActions; }
static PaneActions &paneActions() { return instance().m_paneActions; }
static QList<QKeySequence> shortcutsFor(QAction *action);
static bool triggerAction(QKeyEvent *event);
protected:
void initWidgetActions(const Core::Context &context);
void initPaneActions(const Core::Context &context);
void initGlobalCommands();
private:
WidgetActions m_widgetActions;
PaneActions m_paneActions;
QList<Core::Command *> m_commands;
};
} // namespace Terminal

View File

@@ -4,9 +4,9 @@
#include "terminalpane.h"
#include "shellmodel.h"
#include "terminalcommands.h"
#include "terminaltr.h"
#include "terminalwidget.h"
#include "utils/terminalhooks.h"
#include <coreplugin/actionmanager/actionmanager.h>
#include <coreplugin/icontext.h>
@@ -16,10 +16,12 @@
#include <utils/algorithm.h>
#include <utils/environment.h>
#include <utils/terminalhooks.h>
#include <utils/utilsicons.h>
#include <QMenu>
#include <QStandardPaths>
#include <QtWidgets/qwidget.h>
namespace Terminal {
@@ -28,19 +30,33 @@ using namespace Utils::Terminal;
TerminalPane::TerminalPane(QObject *parent)
: Core::IOutputPane(parent)
, m_tabWidget(new QTabWidget)
{
Core::Context context("Terminal.Window");
setupContext("Terminal.Pane", m_tabWidget);
TerminalCommands::instance().init(Core::Context("Terminal.Pane"));
m_newTerminal.setIcon(Icons::PLUS_TOOLBAR.icon());
m_newTerminal.setToolTip(Tr::tr("Create a new Terminal."));
connect(this, &IOutputPane::zoomInRequested, this, [this] {
if (currentTerminal())
currentTerminal()->zoomIn();
});
connect(this, &IOutputPane::zoomOutRequested, this, [this] {
if (currentTerminal())
currentTerminal()->zoomOut();
});
connect(&m_newTerminal, &QAction::triggered, this, [this] { openTerminal({}); });
QAction &newTerminal = TerminalCommands::instance().paneActions().newTerminal;
QAction &closeTerminal = TerminalCommands::instance().paneActions().closeTerminal;
m_closeTerminal.setIcon(Icons::CLOSE_TOOLBAR.icon());
m_closeTerminal.setToolTip(Tr::tr("Close the current Terminal."));
m_closeTerminal.setEnabled(false);
newTerminal.setIcon(Icons::PLUS_TOOLBAR.icon());
newTerminal.setToolTip(Tr::tr("Create a new Terminal."));
connect(&m_closeTerminal, &QAction::triggered, this, [this] {
connect(&newTerminal, &QAction::triggered, this, [this] { openTerminal({}); });
closeTerminal.setIcon(Icons::CLOSE_TOOLBAR.icon());
closeTerminal.setToolTip(Tr::tr("Close the current Terminal."));
closeTerminal.setEnabled(false);
connect(&closeTerminal, &QAction::triggered, this, [this] {
removeTab(m_tabWidget->currentIndex());
});
@@ -68,40 +84,40 @@ TerminalPane::TerminalPane(QObject *parent)
addItems(shellModel->remote());
});
m_newTerminal.setMenu(shellMenu);
m_newTerminal.setShortcut(QKeySequence(
HostOsInfo::isMacHost() ? QLatin1String("Ctrl+T") : QLatin1String("Ctrl+Shift+T")));
newTerminal.setMenu(shellMenu);
m_newTerminalButton->setDefaultAction(&m_newTerminal);
m_newTerminalButton->setDefaultAction(&newTerminal);
m_closeTerminalButton = new QToolButton();
m_closeTerminalButton->setDefaultAction(&m_closeTerminal);
m_closeTerminalButton->setDefaultAction(&closeTerminal);
m_nextTerminal.setShortcut(QKeySequence(HostOsInfo::isMacHost() ? QLatin1String("Ctrl+Shift+[")
: QLatin1String("Ctrl+PgUp")));
m_prevTerminal.setShortcut(QKeySequence(HostOsInfo::isMacHost()
? QLatin1String("Ctrl+Shift+]")
: QLatin1String("Ctrl+PgDown")));
connect(&TerminalCommands::instance().paneActions().nextTerminal,
&QAction::triggered,
this,
[this] {
if (canNavigate())
goToNext();
});
connect(&TerminalCommands::instance().paneActions().prevTerminal,
&QAction::triggered,
this,
[this] {
if (canPrevious())
goToPrev();
});
connect(&m_nextTerminal, &QAction::triggered, this, [this] {
if (canNavigate())
goToNext();
});
connect(&m_prevTerminal, &QAction::triggered, this, [this] {
if (canPrevious())
goToPrev();
});
m_minMaxAction.setShortcut(QKeySequence(HostOsInfo::isMacHost() ? QLatin1String("Ctrl+Return")
: QLatin1String("Alt+Return")));
connect(&m_minMaxAction, &QAction::triggered, this, []() {
connect(&TerminalCommands::instance().paneActions().minMax, &QAction::triggered, this, []() {
Core::Command *minMaxCommand = Core::ActionManager::command("Coreplugin.OutputPane.minmax");
if (minMaxCommand)
emit minMaxCommand->action()->triggered();
});
}
TerminalPane::~TerminalPane()
{
delete m_tabWidget;
}
static std::optional<FilePath> startupProjectDirectory()
{
ProjectExplorer::Project *project = ProjectExplorer::ProjectManager::startupProject();
@@ -132,7 +148,7 @@ void TerminalPane::openTerminal(const OpenTerminalParameters &parameters)
m_tabWidget->currentWidget()->setFocus();
m_closeTerminal.setEnabled(m_tabWidget->count() > 1);
TerminalCommands::instance().paneActions().closeTerminal.setEnabled(m_tabWidget->count() > 1);
emit navigateStateUpdate();
}
@@ -142,7 +158,7 @@ void TerminalPane::addTerminal(TerminalWidget *terminal, const QString &title)
m_tabWidget->setCurrentIndex(m_tabWidget->addTab(terminal, title));
setupTerminalWidget(terminal);
m_closeTerminal.setEnabled(m_tabWidget->count() > 1);
TerminalCommands::instance().paneActions().closeTerminal.setEnabled(m_tabWidget->count() > 1);
emit navigateStateUpdate();
}
@@ -161,9 +177,8 @@ TerminalWidget *TerminalPane::stoppedTerminalWithId(const Id &identifier) const
QWidget *TerminalPane::outputWidget(QWidget *parent)
{
if (!m_tabWidget) {
m_tabWidget = new QTabWidget(parent);
if (!m_widgetInitialized) {
m_widgetInitialized = true;
m_tabWidget->setTabBarAutoHide(true);
m_tabWidget->setDocumentMode(true);
m_tabWidget->setTabsClosable(true);
@@ -192,7 +207,7 @@ void TerminalPane::removeTab(int index)
if (m_tabWidget->count() > 1)
delete m_tabWidget->widget(index);
m_closeTerminal.setEnabled(m_tabWidget->count() > 1);
TerminalCommands::instance().paneActions().closeTerminal.setEnabled(m_tabWidget->count() > 1);
emit navigateStateUpdate();
}
@@ -229,13 +244,15 @@ void TerminalPane::setupTerminalWidget(TerminalWidget *terminal)
if (!terminal->shellName().isEmpty())
setTabText(terminal);
terminal->addActions({&m_newTerminal, &m_nextTerminal, &m_prevTerminal, &m_minMaxAction});
}
QList<QWidget *> TerminalPane::toolBarWidgets() const
{
return {m_newTerminalButton, m_closeTerminalButton};
QList<QWidget *> widgets = IOutputPane::toolBarWidgets();
widgets.prepend(m_newTerminalButton);
widgets.prepend(m_closeTerminalButton);
return widgets;
}
QString TerminalPane::displayName() const

View File

@@ -20,21 +20,22 @@ class TerminalPane : public Core::IOutputPane
Q_OBJECT
public:
TerminalPane(QObject *parent = nullptr);
~TerminalPane() override;
virtual QWidget *outputWidget(QWidget *parent);
virtual QList<QWidget *> toolBarWidgets() const;
virtual QString displayName() const;
virtual int priorityInStatusBar() const;
virtual void clearContents();
virtual void visibilityChanged(bool visible);
virtual void setFocus();
virtual bool hasFocus() const;
virtual bool canFocus() const;
virtual bool canNavigate() const;
virtual bool canNext() const;
virtual bool canPrevious() const;
virtual void goToNext();
virtual void goToPrev();
QWidget *outputWidget(QWidget *parent) override;
QList<QWidget *> toolBarWidgets() const override;
QString displayName() const override;
int priorityInStatusBar() const override;
void clearContents() override;
void visibilityChanged(bool visible) override;
void setFocus() override;
bool hasFocus() const override;
bool canFocus() const override;
bool canNavigate() const override;
bool canNext() const override;
bool canPrevious() const override;
void goToNext() override;
void goToPrev() override;
void openTerminal(const Utils::Terminal::OpenTerminalParameters &parameters);
void addTerminal(TerminalWidget *terminal, const QString &title);
@@ -53,11 +54,7 @@ private:
QToolButton *m_newTerminalButton{nullptr};
QToolButton *m_closeTerminalButton{nullptr};
QAction m_newTerminal;
QAction m_closeTerminal;
QAction m_nextTerminal;
QAction m_prevTerminal;
QAction m_minMaxAction;
bool m_widgetInitialized{false};
};
} // namespace Terminal

View File

@@ -3,9 +3,9 @@
#include "terminalwidget.h"
#include "glyphcache.h"
#include "terminalcommands.h"
#include "terminalsettings.h"
#include "terminalsurface.h"
#include "terminaltr.h"
#include <coreplugin/editormanager/editormanager.h>
#include <coreplugin/icore.h>
@@ -53,11 +53,6 @@ static constexpr std::chrono::milliseconds minRefreshInterval = 1s / 30;
TerminalWidget::TerminalWidget(QWidget *parent, const OpenTerminalParameters &openParameters)
: QAbstractScrollArea(parent)
, m_copyAction(Tr::tr("Copy"))
, m_pasteAction(Tr::tr("Paste"))
, m_clearSelectionAction(Tr::tr("Clear Selection"))
, m_zoomInAction(Tr::tr("Zoom In"))
, m_zoomOutAction(Tr::tr("Zoom Out"))
, m_openParameters(openParameters)
, m_lastFlush(std::chrono::system_clock::now())
, m_lastDoubleClick(std::chrono::system_clock::now())
@@ -224,27 +219,20 @@ void TerminalWidget::setupColors()
void TerminalWidget::setupActions()
{
m_copyAction.setEnabled(false);
m_copyAction.setShortcuts(
{QKeySequence(HostOsInfo::isMacHost() ? QLatin1String("Ctrl+C")
: QLatin1String("Ctrl+Shift+C")),
QKeySequence(Qt::Key_Return)});
m_pasteAction.setShortcut(QKeySequence(
HostOsInfo::isMacHost() ? QLatin1String("Ctrl+V") : QLatin1String("Ctrl+Shift+V")));
WidgetActions &a = TerminalCommands::widgetActions();
m_clearSelectionAction.setShortcut(QKeySequence("Esc"));
auto ifHasFocus = [this](void (TerminalWidget::*f)()) {
return [this, f] {
if (hasFocus())
(this->*f)();
};
};
m_zoomInAction.setShortcuts({QKeySequence("Ctrl++"), QKeySequence("Ctrl+Shift++")});
m_zoomOutAction.setShortcut(QKeySequence("Ctrl+-"));
connect(&m_copyAction, &QAction::triggered, this, &TerminalWidget::copyToClipboard);
connect(&m_pasteAction, &QAction::triggered, this, &TerminalWidget::pasteFromClipboard);
connect(&m_clearSelectionAction, &QAction::triggered, this, &TerminalWidget::clearSelection);
connect(&m_zoomInAction, &QAction::triggered, this, &TerminalWidget::zoomIn);
connect(&m_zoomOutAction, &QAction::triggered, this, &TerminalWidget::zoomOut);
addActions(
{&m_copyAction, &m_pasteAction, &m_clearSelectionAction, &m_zoomInAction, &m_zoomOutAction});
// clang-format off
connect(&a.copy, &QAction::triggered, this, ifHasFocus(&TerminalWidget::copyToClipboard));
connect(&a.paste, &QAction::triggered, this, ifHasFocus(&TerminalWidget::pasteFromClipboard));
connect(&a.clearSelection, &QAction::triggered, this, ifHasFocus(&TerminalWidget::clearSelection));
// clang-format on
}
void TerminalWidget::writeToPty(const QByteArray &data)
@@ -342,6 +330,14 @@ QColor TerminalWidget::toQColor(std::variant<int, QColor> color) const
return std::get<QColor>(color);
}
void TerminalWidget::updateCopyState()
{
if (!hasFocus())
return;
TerminalCommands::widgetActions().copy.setEnabled(m_selection.has_value());
}
void TerminalWidget::setFont(const QFont &font)
{
m_font = font;
@@ -364,33 +360,10 @@ void TerminalWidget::setFont(const QFont &font)
}
}
QAction &TerminalWidget::copyAction()
void TerminalWidget::copyToClipboard()
{
return m_copyAction;
}
QTC_ASSERT(m_selection.has_value(), return);
QAction &TerminalWidget::pasteAction()
{
return m_pasteAction;
}
QAction &TerminalWidget::clearSelectionAction()
{
return m_clearSelectionAction;
}
QAction &TerminalWidget::zoomInAction()
{
return m_zoomInAction;
}
QAction &TerminalWidget::zoomOutAction()
{
return m_zoomOutAction;
}
void TerminalWidget::copyToClipboard() const
{
QString text = textFromSelection();
qCDebug(selectionLog) << "Copied to clipboard: " << text;
@@ -491,10 +464,10 @@ bool TerminalWidget::setSelection(const std::optional<Selection> &selection)
return false;
}
m_copyAction.setEnabled(selection.has_value());
m_selection = selection;
updateCopyState();
if (m_selection && m_selection->final) {
qCDebug(selectionLog) << "Copy enabled:" << selection.has_value();
@@ -958,25 +931,7 @@ void TerminalWidget::keyPressEvent(QKeyEvent *event)
m_cursorBlinkState = true;
}
bool actionTriggered = false;
for (const auto &action : actions()) {
if (!action->isEnabled())
continue;
for (const auto &shortcut : action->shortcuts()) {
const auto result = shortcut.matches(QKeySequence(event->keyCombination()));
if (result == QKeySequence::ExactMatch) {
action->trigger();
actionTriggered = true;
break;
}
}
if (actionTriggered)
break;
}
if (actionTriggered) {
if (TerminalCommands::triggerAction(event)) {
setSelection(std::nullopt);
return;
}
@@ -1067,6 +1022,7 @@ void TerminalWidget::focusInEvent(QFocusEvent *)
{
updateViewport();
configBlinkTimer();
updateCopyState();
}
void TerminalWidget::focusOutEvent(QFocusEvent *)
{
@@ -1123,10 +1079,10 @@ void TerminalWidget::mousePressEvent(QMouseEvent *event)
updateViewport();
} else if (event->button() == Qt::RightButton) {
if (m_selection) {
m_copyAction.trigger();
copyToClipboard();
setSelection(std::nullopt);
} else {
m_pasteAction.trigger();
pasteFromClipboard();
}
}
}

View File

@@ -29,15 +29,7 @@ public:
void setFont(const QFont &font);
QAction &copyAction();
QAction &pasteAction();
QAction &clearSelectionAction();
QAction &zoomInAction();
QAction &zoomOutAction();
void copyToClipboard() const;
void copyToClipboard();
void pasteFromClipboard();
void clearSelection();
@@ -168,6 +160,8 @@ protected:
QColor toQColor(std::variant<int, QColor> color) const;
void updateCopyState();
private:
std::unique_ptr<Utils::QtcProcess> m_process;
std::unique_ptr<Internal::TerminalSurface> m_surface;
@@ -192,14 +186,6 @@ private:
QPoint end;
} m_activeMouseSelect;
QAction m_copyAction;
QAction m_pasteAction;
QAction m_clearSelectionAction;
QAction m_zoomInAction;
QAction m_zoomOutAction;
QTimer m_flushDelayTimer;
QTimer m_scrollTimer;