forked from qt-creator/qt-creator
Terminal: lock/unlock keyboard
We copied QShortCutMap into Qtc to allow us tight control over which shortcuts are "enabled" while the focus is inside a terminal, and the keyboard is "locked" to the Terminal. Locked here means that except for a select few, all key presses are send directly to the terminal and cannot be used to activate other actions. Change-Id: I96cddf753033c0f4e7d806b20085bb4755853117 Reviewed-by: Eike Ziller <eike.ziller@qt.io>
This commit is contained in:
@@ -92,6 +92,8 @@ protected:
|
||||
void setupContext(const Context &context, QWidget *widget);
|
||||
void setZoomButtonsEnabled(bool enabled);
|
||||
|
||||
IContext *m_context = nullptr;
|
||||
|
||||
private:
|
||||
virtual void updateFilter();
|
||||
|
||||
@@ -108,7 +110,6 @@ private:
|
||||
QAction *m_filterActionCaseSensitive = nullptr;
|
||||
QAction *m_invertFilterAction = nullptr;
|
||||
Utils::FancyLineEdit *m_filterOutputLineEdit = nullptr;
|
||||
IContext *m_context = nullptr;
|
||||
bool m_filterRegexp = false;
|
||||
bool m_invertFilter = false;
|
||||
Qt::CaseSensitivity m_filterCaseSensitivity = Qt::CaseInsensitive;
|
||||
|
@@ -9,6 +9,7 @@ add_qtc_plugin(Terminal
|
||||
scrollback.cpp scrollback.h
|
||||
shellintegration.cpp shellintegration.h
|
||||
shellmodel.cpp shellmodel.h
|
||||
shortcutmap.cpp shortcutmap.h
|
||||
terminal.qrc
|
||||
terminalconstants.h
|
||||
terminalicons.h
|
||||
|
557
src/plugins/terminal/shortcutmap.cpp
Normal file
557
src/plugins/terminal/shortcutmap.cpp
Normal file
@@ -0,0 +1,557 @@
|
||||
// Copyright (C) 2023 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
|
||||
|
||||
// COPIED FROM qshortcutmap.cpp
|
||||
|
||||
#include "shortcutmap.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include <QGuiApplication>
|
||||
#include <QKeyEvent>
|
||||
#include <QLoggingCategory>
|
||||
#include <QWindow>
|
||||
|
||||
Q_LOGGING_CATEGORY(lcShortcutMap, "terminal.shortcutmap", QtWarningMsg)
|
||||
|
||||
namespace Terminal::Internal {
|
||||
|
||||
/* \internal
|
||||
Entry data for ShortcutMap
|
||||
Contains:
|
||||
Keysequence for entry
|
||||
Pointer to parent owning the sequence
|
||||
*/
|
||||
struct ShortcutEntry
|
||||
{
|
||||
ShortcutEntry()
|
||||
: keyseq(0)
|
||||
, context(Qt::WindowShortcut)
|
||||
, enabled(false)
|
||||
, autorepeat(1)
|
||||
, id(0)
|
||||
, owner(nullptr)
|
||||
, contextMatcher(nullptr)
|
||||
{}
|
||||
|
||||
ShortcutEntry(const QKeySequence &k)
|
||||
: keyseq(k)
|
||||
, context(Qt::WindowShortcut)
|
||||
, enabled(false)
|
||||
, autorepeat(1)
|
||||
, id(0)
|
||||
, owner(nullptr)
|
||||
, contextMatcher(nullptr)
|
||||
{}
|
||||
|
||||
ShortcutEntry(QObject *o,
|
||||
const QKeySequence &k,
|
||||
Qt::ShortcutContext c,
|
||||
int i,
|
||||
bool a,
|
||||
ShortcutMap::ContextMatcher m)
|
||||
: keyseq(k)
|
||||
, context(c)
|
||||
, enabled(true)
|
||||
, autorepeat(a)
|
||||
, id(i)
|
||||
, owner(o)
|
||||
, contextMatcher(m)
|
||||
{}
|
||||
|
||||
bool correctContext() const { return contextMatcher(owner, context); }
|
||||
|
||||
bool operator<(const ShortcutEntry &f) const { return keyseq < f.keyseq; }
|
||||
|
||||
QKeySequence keyseq;
|
||||
Qt::ShortcutContext context;
|
||||
bool enabled : 1;
|
||||
bool autorepeat : 1;
|
||||
signed int id;
|
||||
QObject *owner;
|
||||
ShortcutMap::ContextMatcher contextMatcher;
|
||||
};
|
||||
|
||||
/* \internal
|
||||
Private data for ShortcutMap
|
||||
*/
|
||||
class ShortcutMapPrivate
|
||||
{
|
||||
Q_DECLARE_PUBLIC(ShortcutMap)
|
||||
|
||||
public:
|
||||
ShortcutMapPrivate(ShortcutMap *parent)
|
||||
: q_ptr(parent)
|
||||
, currentId(0)
|
||||
, ambigCount(0)
|
||||
, currentState(QKeySequence::NoMatch)
|
||||
{
|
||||
identicals.reserve(10);
|
||||
currentSequences.reserve(10);
|
||||
}
|
||||
ShortcutMap *q_ptr; // Private's parent
|
||||
|
||||
QList<ShortcutEntry> sequences; // All sequences!
|
||||
|
||||
int currentId; // Global shortcut ID number
|
||||
int ambigCount; // Index of last enabled ambiguous dispatch
|
||||
QKeySequence::SequenceMatch currentState;
|
||||
QList<QKeySequence> currentSequences; // Sequence for the current state
|
||||
QList<QKeySequence> newEntries;
|
||||
QKeySequence prevSequence; // Sequence for the previous identical match
|
||||
QList<const ShortcutEntry *> identicals; // Last identical matches
|
||||
};
|
||||
|
||||
/*! \internal
|
||||
ShortcutMap constructor.
|
||||
*/
|
||||
ShortcutMap::ShortcutMap()
|
||||
: d_ptr(new ShortcutMapPrivate(this))
|
||||
{
|
||||
resetState();
|
||||
}
|
||||
|
||||
/*! \internal
|
||||
ShortcutMap destructor.
|
||||
*/
|
||||
ShortcutMap::~ShortcutMap() {}
|
||||
|
||||
/*! \internal
|
||||
Adds a shortcut to the global map.
|
||||
Returns the id of the newly added shortcut.
|
||||
*/
|
||||
int ShortcutMap::addShortcut(QObject *owner,
|
||||
const QKeySequence &key,
|
||||
Qt::ShortcutContext context,
|
||||
ContextMatcher matcher)
|
||||
{
|
||||
Q_ASSERT_X(owner, "ShortcutMap::addShortcut", "All shortcuts need an owner");
|
||||
Q_ASSERT_X(!key.isEmpty(), "ShortcutMap::addShortcut", "Cannot add keyless shortcuts to map");
|
||||
Q_D(ShortcutMap);
|
||||
|
||||
ShortcutEntry newEntry(owner, key, context, --(d->currentId), true, matcher);
|
||||
const auto it = std::upper_bound(d->sequences.begin(), d->sequences.end(), newEntry);
|
||||
d->sequences.insert(it, newEntry); // Insert sorted
|
||||
qCDebug(lcShortcutMap).nospace() << "ShortcutMap::addShortcut(" << owner << ", " << key << ", "
|
||||
<< context << ") added shortcut with ID " << d->currentId;
|
||||
return d->currentId;
|
||||
}
|
||||
|
||||
/*! \internal
|
||||
Removes a shortcut from the global map.
|
||||
If \a owner is \nullptr, all entries in the map with the key sequence specified
|
||||
is removed. If \a key is null, all sequences for \a owner is removed from
|
||||
the map. If \a id is 0, any identical \a key sequences owned by \a owner
|
||||
are removed.
|
||||
Returns the number of sequences removed from the map.
|
||||
*/
|
||||
|
||||
int ShortcutMap::removeShortcut(int id, QObject *owner, const QKeySequence &key)
|
||||
{
|
||||
Q_D(ShortcutMap);
|
||||
int itemsRemoved = 0;
|
||||
bool allOwners = (owner == nullptr);
|
||||
bool allKeys = key.isEmpty();
|
||||
bool allIds = id == 0;
|
||||
|
||||
auto debug = qScopeGuard([&]() {
|
||||
qCDebug(lcShortcutMap).nospace()
|
||||
<< "ShortcutMap::removeShortcut(" << id << ", " << owner << ", " << key << ") removed "
|
||||
<< itemsRemoved << " shortcuts(s)";
|
||||
});
|
||||
|
||||
// Special case, remove everything
|
||||
if (allOwners && allKeys && allIds) {
|
||||
itemsRemoved = d->sequences.size();
|
||||
d->sequences.clear();
|
||||
return itemsRemoved;
|
||||
}
|
||||
|
||||
int i = d->sequences.size() - 1;
|
||||
while (i >= 0) {
|
||||
const ShortcutEntry &entry = d->sequences.at(i);
|
||||
int entryId = entry.id;
|
||||
if ((allOwners || entry.owner == owner) && (allIds || entry.id == id)
|
||||
&& (allKeys || entry.keyseq == key)) {
|
||||
d->sequences.removeAt(i);
|
||||
++itemsRemoved;
|
||||
}
|
||||
if (id == entryId)
|
||||
return itemsRemoved;
|
||||
--i;
|
||||
}
|
||||
return itemsRemoved;
|
||||
}
|
||||
|
||||
/*! \internal
|
||||
Resets the state of the statemachine to NoMatch
|
||||
*/
|
||||
void ShortcutMap::resetState()
|
||||
{
|
||||
Q_D(ShortcutMap);
|
||||
d->currentState = QKeySequence::NoMatch;
|
||||
clearSequence(d->currentSequences);
|
||||
}
|
||||
|
||||
/*! \internal
|
||||
Returns the current state of the statemachine
|
||||
*/
|
||||
QKeySequence::SequenceMatch ShortcutMap::state()
|
||||
{
|
||||
Q_D(ShortcutMap);
|
||||
return d->currentState;
|
||||
}
|
||||
|
||||
/*! \internal
|
||||
Uses nextState(QKeyEvent) to check for a grabbed shortcut.
|
||||
|
||||
If so, it is dispatched using dispatchEvent().
|
||||
|
||||
Returns true if a shortcut handled the event.
|
||||
|
||||
\sa nextState, dispatchEvent
|
||||
*/
|
||||
bool ShortcutMap::tryShortcut(QKeyEvent *e)
|
||||
{
|
||||
Q_D(ShortcutMap);
|
||||
|
||||
if (e->key() == Qt::Key_unknown)
|
||||
return false;
|
||||
|
||||
QKeySequence::SequenceMatch previousState = state();
|
||||
|
||||
switch (nextState(e)) {
|
||||
case QKeySequence::NoMatch:
|
||||
// In the case of going from a partial match to no match we handled the
|
||||
// event, since we already stated that we did for the partial match. But
|
||||
// in the normal case of directly going to no match we say we didn't.
|
||||
return previousState == QKeySequence::PartialMatch;
|
||||
case QKeySequence::PartialMatch:
|
||||
// For a partial match we don't know yet if we will handle the shortcut
|
||||
// but we need to say we did, so that we get the follow-up key-presses.
|
||||
return true;
|
||||
case QKeySequence::ExactMatch: {
|
||||
// Save number of identical matches before dispatching
|
||||
// to keep ShortcutMap and tryShortcut reentrant.
|
||||
const int identicalMatches = d->identicals.size();
|
||||
resetState();
|
||||
dispatchEvent(e);
|
||||
// If there are no identicals we've only found disabled shortcuts, and
|
||||
// shouldn't say that we handled the event.
|
||||
return identicalMatches > 0;
|
||||
}
|
||||
}
|
||||
Q_UNREACHABLE_RETURN(false);
|
||||
}
|
||||
|
||||
/*! \internal
|
||||
Returns the next state of the statemachine
|
||||
If return value is SequenceMatch::ExactMatch, then a call to matches()
|
||||
will return a QObjects* list of all matching objects for the last matching
|
||||
sequence.
|
||||
*/
|
||||
QKeySequence::SequenceMatch ShortcutMap::nextState(QKeyEvent *e)
|
||||
{
|
||||
Q_D(ShortcutMap);
|
||||
// Modifiers can NOT be shortcuts...
|
||||
if (e->key() >= Qt::Key_Shift && e->key() <= Qt::Key_ScrollLock)
|
||||
return d->currentState;
|
||||
|
||||
QKeySequence::SequenceMatch result = QKeySequence::NoMatch;
|
||||
|
||||
// We start fresh each time..
|
||||
d->identicals.clear();
|
||||
|
||||
result = find(e);
|
||||
if (result == QKeySequence::NoMatch && (e->modifiers() & Qt::KeypadModifier)) {
|
||||
// Try to find a match without keypad modifier
|
||||
result = find(e, Qt::KeypadModifier);
|
||||
}
|
||||
if (result == QKeySequence::NoMatch && e->modifiers() & Qt::ShiftModifier) {
|
||||
// If Shift + Key_Backtab, also try Shift + Qt::Key_Tab
|
||||
if (e->key() == Qt::Key_Backtab) {
|
||||
QKeyEvent pe = QKeyEvent(e->type(), Qt::Key_Tab, e->modifiers(), e->text());
|
||||
result = find(&pe);
|
||||
}
|
||||
}
|
||||
|
||||
// Does the new state require us to clean up?
|
||||
if (result == QKeySequence::NoMatch)
|
||||
clearSequence(d->currentSequences);
|
||||
d->currentState = result;
|
||||
|
||||
qCDebug(lcShortcutMap).nospace() << "ShortcutMap::nextState(" << e << ") = " << result;
|
||||
return result;
|
||||
}
|
||||
|
||||
/*! \internal
|
||||
Determines if an enabled shortcut has a matching key sequence.
|
||||
*/
|
||||
bool ShortcutMap::hasShortcutForKeySequence(const QKeySequence &seq) const
|
||||
{
|
||||
Q_D(const ShortcutMap);
|
||||
ShortcutEntry entry(seq); // needed for searching
|
||||
const auto itEnd = d->sequences.cend();
|
||||
auto it = std::lower_bound(d->sequences.cbegin(), itEnd, entry);
|
||||
|
||||
for (; it != itEnd; ++it) {
|
||||
if (matches(entry.keyseq, (*it).keyseq) == QKeySequence::ExactMatch
|
||||
&& (*it).correctContext() && (*it).enabled) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
//end of the loop: we didn't find anything
|
||||
return false;
|
||||
}
|
||||
|
||||
/*! \internal
|
||||
Returns the next state of the statemachine, based
|
||||
on the new key event \a e.
|
||||
Matches are appended to the list of identicals,
|
||||
which can be access through matches().
|
||||
\sa matches
|
||||
*/
|
||||
QKeySequence::SequenceMatch ShortcutMap::find(QKeyEvent *e, int ignoredModifiers)
|
||||
{
|
||||
Q_D(ShortcutMap);
|
||||
if (!d->sequences.size())
|
||||
return QKeySequence::NoMatch;
|
||||
|
||||
createNewSequences(e, d->newEntries, ignoredModifiers);
|
||||
qCDebug(lcShortcutMap) << "Possible shortcut key sequences:" << d->newEntries;
|
||||
|
||||
// Should never happen
|
||||
if (d->newEntries == d->currentSequences) {
|
||||
Q_ASSERT_X(e->key() != Qt::Key_unknown || e->text().size(),
|
||||
"ShortcutMap::find",
|
||||
"New sequence to find identical to previous");
|
||||
return QKeySequence::NoMatch;
|
||||
}
|
||||
|
||||
// Looking for new identicals, scrap old
|
||||
d->identicals.clear();
|
||||
|
||||
bool partialFound = false;
|
||||
bool identicalDisabledFound = false;
|
||||
QList<QKeySequence> okEntries;
|
||||
int result = QKeySequence::NoMatch;
|
||||
for (int i = d->newEntries.size() - 1; i >= 0; --i) {
|
||||
ShortcutEntry entry(d->newEntries.at(i)); // needed for searching
|
||||
qCDebug(lcShortcutMap) << "- checking entry" << entry.id << entry.keyseq;
|
||||
const auto itEnd = d->sequences.constEnd();
|
||||
auto it = std::lower_bound(d->sequences.constBegin(), itEnd, entry);
|
||||
|
||||
int oneKSResult = QKeySequence::NoMatch;
|
||||
int tempRes = QKeySequence::NoMatch;
|
||||
do {
|
||||
if (it == itEnd)
|
||||
break;
|
||||
tempRes = matches(entry.keyseq, (*it).keyseq);
|
||||
oneKSResult = qMax(oneKSResult, tempRes);
|
||||
qCDebug(lcShortcutMap) << " - matches returned" << tempRes << "for" << entry.keyseq
|
||||
<< it->keyseq << "- correctContext()?" << it->correctContext();
|
||||
if (tempRes != QKeySequence::NoMatch && (*it).correctContext()) {
|
||||
if (tempRes == QKeySequence::ExactMatch) {
|
||||
if ((*it).enabled)
|
||||
d->identicals.append(&*it);
|
||||
else
|
||||
identicalDisabledFound = true;
|
||||
} else if (tempRes == QKeySequence::PartialMatch) {
|
||||
// We don't need partials, if we have identicals
|
||||
if (d->identicals.size())
|
||||
break;
|
||||
// We only care about enabled partials, so we don't consume
|
||||
// key events when all partials are disabled!
|
||||
partialFound |= (*it).enabled;
|
||||
}
|
||||
}
|
||||
++it;
|
||||
// If we got a valid match on this run, there might still be more keys to check against,
|
||||
// so we'll loop once more. If we get NoMatch, there's guaranteed no more possible
|
||||
// matches in the shortcutmap.
|
||||
} while (tempRes != QKeySequence::NoMatch);
|
||||
|
||||
// If the type of match improves (ergo, NoMatch->Partial, or Partial->Exact), clear the
|
||||
// previous list. If this match is equal or better than the last match, append to the list
|
||||
if (oneKSResult > result) {
|
||||
okEntries.clear();
|
||||
qCDebug(lcShortcutMap)
|
||||
<< "Found better match (" << d->newEntries << "), clearing key sequence list";
|
||||
}
|
||||
if (oneKSResult && oneKSResult >= result) {
|
||||
okEntries << d->newEntries.at(i);
|
||||
qCDebug(lcShortcutMap) << "Added ok key sequence" << d->newEntries;
|
||||
}
|
||||
}
|
||||
|
||||
if (d->identicals.size()) {
|
||||
result = QKeySequence::ExactMatch;
|
||||
} else if (partialFound) {
|
||||
result = QKeySequence::PartialMatch;
|
||||
} else if (identicalDisabledFound) {
|
||||
result = QKeySequence::ExactMatch;
|
||||
} else {
|
||||
clearSequence(d->currentSequences);
|
||||
result = QKeySequence::NoMatch;
|
||||
}
|
||||
if (result != QKeySequence::NoMatch)
|
||||
d->currentSequences = okEntries;
|
||||
qCDebug(lcShortcutMap) << "Returning shortcut match == " << result;
|
||||
return QKeySequence::SequenceMatch(result);
|
||||
}
|
||||
|
||||
/*! \internal
|
||||
Clears \a seq to an empty QKeySequence.
|
||||
Same as doing (the slower)
|
||||
\snippet code/src_gui_kernel_shortcutmap.cpp 0
|
||||
*/
|
||||
void ShortcutMap::clearSequence(QList<QKeySequence> &ksl)
|
||||
{
|
||||
ksl.clear();
|
||||
d_func()->newEntries.clear();
|
||||
}
|
||||
|
||||
static QList<int> extractKeyFromEvent(QKeyEvent *e)
|
||||
{
|
||||
QList<int> result;
|
||||
if (e->key() && (e->key() != Qt::Key_unknown))
|
||||
result << e->keyCombination().toCombined();
|
||||
else if (!e->text().isEmpty())
|
||||
result << int(e->text().at(0).unicode() + (int) e->modifiers());
|
||||
return result;
|
||||
}
|
||||
|
||||
/*! \internal
|
||||
Alters \a seq to the new sequence state, based on the
|
||||
current sequence state, and the new key event \a e.
|
||||
*/
|
||||
void ShortcutMap::createNewSequences(QKeyEvent *e, QList<QKeySequence> &ksl, int ignoredModifiers)
|
||||
{
|
||||
Q_D(ShortcutMap);
|
||||
|
||||
QList<int> possibleKeys = extractKeyFromEvent(e);
|
||||
qCDebug(lcShortcutMap) << "Creating new sequences for" << e
|
||||
<< "with ignoredModifiers=" << Qt::KeyboardModifiers(ignoredModifiers);
|
||||
int pkTotal = possibleKeys.size();
|
||||
if (!pkTotal)
|
||||
return;
|
||||
|
||||
int ssActual = d->currentSequences.size();
|
||||
int ssTotal = qMax(1, ssActual);
|
||||
// Resize to possible permutations of the current sequence(s).
|
||||
ksl.resize(pkTotal * ssTotal);
|
||||
|
||||
int index = ssActual ? d->currentSequences.at(0).count() : 0;
|
||||
for (int pkNum = 0; pkNum < pkTotal; ++pkNum) {
|
||||
for (int ssNum = 0; ssNum < ssTotal; ++ssNum) {
|
||||
int i = (pkNum * ssTotal) + ssNum;
|
||||
QKeySequence &curKsl = ksl[i];
|
||||
if (ssActual) {
|
||||
const QKeySequence &curSeq = d->currentSequences.at(ssNum);
|
||||
curKsl = QKeySequence(curSeq[0], curSeq[1], curSeq[2], curSeq[3]);
|
||||
} else {
|
||||
curKsl = QKeySequence(QKeyCombination::fromCombined(0));
|
||||
}
|
||||
|
||||
std::array<QKeyCombination, 4> cur = {curKsl[0], curKsl[1], curKsl[2], curKsl[3]};
|
||||
cur[index] = QKeyCombination::fromCombined(possibleKeys.at(pkNum) & ~ignoredModifiers);
|
||||
curKsl = QKeySequence(cur[0], cur[1], cur[2], cur[3]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*! \internal
|
||||
Basically the same function as QKeySequence::matches(const QKeySequence &seq) const
|
||||
only that is specially handles Key_hyphen as Key_Minus, as people mix these up all the time and
|
||||
they conceptually the same.
|
||||
*/
|
||||
QKeySequence::SequenceMatch ShortcutMap::matches(const QKeySequence &seq1,
|
||||
const QKeySequence &seq2) const
|
||||
{
|
||||
uint userN = seq1.count(), seqN = seq2.count();
|
||||
|
||||
if (userN > seqN)
|
||||
return QKeySequence::NoMatch;
|
||||
|
||||
// If equal in length, we have a potential ExactMatch sequence,
|
||||
// else we already know it can only be partial.
|
||||
QKeySequence::SequenceMatch match = (userN == seqN ? QKeySequence::ExactMatch
|
||||
: QKeySequence::PartialMatch);
|
||||
|
||||
for (uint i = 0; i < userN; ++i) {
|
||||
int userKey = seq1[i].toCombined(), sequenceKey = seq2[i].toCombined();
|
||||
if ((userKey & Qt::Key_unknown) == Qt::Key_hyphen)
|
||||
userKey = (userKey & Qt::KeyboardModifierMask) | Qt::Key_Minus;
|
||||
if ((sequenceKey & Qt::Key_unknown) == Qt::Key_hyphen)
|
||||
sequenceKey = (sequenceKey & Qt::KeyboardModifierMask) | Qt::Key_Minus;
|
||||
if (userKey != sequenceKey)
|
||||
return QKeySequence::NoMatch;
|
||||
}
|
||||
return match;
|
||||
}
|
||||
|
||||
/*! \internal
|
||||
Returns the list of ShortcutEntry's matching the last Identical state.
|
||||
*/
|
||||
QList<const ShortcutEntry *> ShortcutMap::matches() const
|
||||
{
|
||||
Q_D(const ShortcutMap);
|
||||
return d->identicals;
|
||||
}
|
||||
|
||||
/*! \internal
|
||||
Dispatches QShortcutEvents to widgets who grabbed the matched key sequence.
|
||||
*/
|
||||
void ShortcutMap::dispatchEvent(QKeyEvent *e)
|
||||
{
|
||||
Q_D(ShortcutMap);
|
||||
if (!d->identicals.size())
|
||||
return;
|
||||
|
||||
const QKeySequence &curKey = d->identicals.at(0)->keyseq;
|
||||
if (d->prevSequence != curKey) {
|
||||
d->ambigCount = 0;
|
||||
d->prevSequence = curKey;
|
||||
}
|
||||
// Find next
|
||||
const ShortcutEntry *current = nullptr, *next = nullptr;
|
||||
int i = 0, enabledShortcuts = 0;
|
||||
QList<const ShortcutEntry *> ambiguousShortcuts;
|
||||
while (i < d->identicals.size()) {
|
||||
current = d->identicals.at(i);
|
||||
if (current->enabled || !next) {
|
||||
++enabledShortcuts;
|
||||
if (lcShortcutMap().isDebugEnabled())
|
||||
ambiguousShortcuts.append(current);
|
||||
if (enabledShortcuts > d->ambigCount + 1)
|
||||
break;
|
||||
next = current;
|
||||
}
|
||||
++i;
|
||||
}
|
||||
d->ambigCount = (d->identicals.size() == i ? 0 : d->ambigCount + 1);
|
||||
// Don't trigger shortcut if we're autorepeating and the shortcut is
|
||||
// grabbed with not accepting autorepeats.
|
||||
if (!next || (e->isAutoRepeat() && !next->autorepeat))
|
||||
return;
|
||||
// Dispatch next enabled
|
||||
if (lcShortcutMap().isDebugEnabled()) {
|
||||
if (ambiguousShortcuts.size() > 1) {
|
||||
qCDebug(lcShortcutMap)
|
||||
<< "The following shortcuts are about to be activated ambiguously:";
|
||||
for (const ShortcutEntry *entry : std::as_const(ambiguousShortcuts))
|
||||
qCDebug(lcShortcutMap).nospace()
|
||||
<< "- " << entry->keyseq << " (belonging to " << entry->owner << ")";
|
||||
}
|
||||
|
||||
qCDebug(lcShortcutMap).nospace()
|
||||
<< "ShortcutMap::dispatchEvent(): Sending QShortcutEvent(\"" << next->keyseq.toString()
|
||||
<< "\", " << next->id << ", " << static_cast<bool>(enabledShortcuts > 1)
|
||||
<< ") to object(" << next->owner << ')';
|
||||
}
|
||||
QShortcutEvent se(next->keyseq, next->id, enabledShortcuts > 1);
|
||||
QCoreApplication::sendEvent(const_cast<QObject *>(next->owner), &se);
|
||||
}
|
||||
|
||||
} // namespace Terminal::Internal
|
52
src/plugins/terminal/shortcutmap.h
Normal file
52
src/plugins/terminal/shortcutmap.h
Normal file
@@ -0,0 +1,52 @@
|
||||
// Copyright (C) 2023 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
|
||||
|
||||
// COPIED FROM shortcutmap_p.h
|
||||
#pragma once
|
||||
|
||||
#include <QKeySequence>
|
||||
#include <QObject>
|
||||
|
||||
class QKeyEvent;
|
||||
class QObject;
|
||||
|
||||
namespace Terminal::Internal {
|
||||
|
||||
struct ShortcutEntry;
|
||||
class ShortcutMapPrivate;
|
||||
|
||||
class ShortcutMap
|
||||
{
|
||||
Q_DECLARE_PRIVATE(ShortcutMap)
|
||||
public:
|
||||
ShortcutMap();
|
||||
~ShortcutMap();
|
||||
|
||||
typedef bool (*ContextMatcher)(QObject *object, Qt::ShortcutContext context);
|
||||
|
||||
int addShortcut(QObject *owner,
|
||||
const QKeySequence &key,
|
||||
Qt::ShortcutContext context,
|
||||
ContextMatcher matcher);
|
||||
int removeShortcut(int id, QObject *owner, const QKeySequence &key = QKeySequence());
|
||||
|
||||
QKeySequence::SequenceMatch state();
|
||||
|
||||
bool tryShortcut(QKeyEvent *e);
|
||||
bool hasShortcutForKeySequence(const QKeySequence &seq) const;
|
||||
|
||||
private:
|
||||
void resetState();
|
||||
QKeySequence::SequenceMatch nextState(QKeyEvent *e);
|
||||
void dispatchEvent(QKeyEvent *e);
|
||||
|
||||
QKeySequence::SequenceMatch find(QKeyEvent *e, int ignoredModifiers = 0);
|
||||
QKeySequence::SequenceMatch matches(const QKeySequence &seq1, const QKeySequence &seq2) const;
|
||||
QList<const ShortcutEntry *> matches() const;
|
||||
void createNewSequences(QKeyEvent *e, QList<QKeySequence> &ksl, int ignoredModifiers);
|
||||
void clearSequence(QList<QKeySequence> &ksl);
|
||||
|
||||
QScopedPointer<ShortcutMapPrivate> d_ptr;
|
||||
};
|
||||
|
||||
} // namespace Terminal::Internal
|
@@ -21,6 +21,8 @@ QtcPlugin {
|
||||
"shellmodel.h",
|
||||
"shellintegration.cpp",
|
||||
"shellintegration.h",
|
||||
"shortcutmap.cpp",
|
||||
"shortcutmap.h",
|
||||
"terminal.qrc",
|
||||
"terminalconstants.h",
|
||||
"terminalicons.h",
|
||||
|
@@ -4,6 +4,7 @@
|
||||
#include "terminalpane.h"
|
||||
|
||||
#include "shellmodel.h"
|
||||
#include "shortcutmap.h"
|
||||
#include "terminalconstants.h"
|
||||
#include "terminalicons.h"
|
||||
#include "terminalsettings.h"
|
||||
@@ -36,9 +37,9 @@ using namespace Core;
|
||||
|
||||
TerminalPane::TerminalPane(QObject *parent)
|
||||
: IOutputPane(parent)
|
||||
, m_context("Terminal.Pane", Core::Constants::C_GLOBAL_CUTOFF)
|
||||
, m_selfContext("Terminal.Pane")
|
||||
{
|
||||
setupContext(m_context, &m_tabWidget);
|
||||
setupContext(m_selfContext, &m_tabWidget);
|
||||
setZoomButtonsEnabled(true);
|
||||
|
||||
connect(this, &IOutputPane::zoomInRequested, this, [this] {
|
||||
@@ -52,6 +53,9 @@ TerminalPane::TerminalPane(QObject *parent)
|
||||
|
||||
initActions();
|
||||
|
||||
m_lockKeyboardButton = new QToolButton();
|
||||
m_lockKeyboardButton->setDefaultAction(&lockKeyboard);
|
||||
|
||||
m_newTerminalButton = new QToolButton();
|
||||
m_newTerminalButton->setDefaultAction(&newTerminal);
|
||||
|
||||
@@ -124,6 +128,14 @@ void TerminalPane::openTerminal(const OpenTerminalParameters ¶meters)
|
||||
}
|
||||
|
||||
const auto terminalWidget = new TerminalWidget(&m_tabWidget, parametersCopy);
|
||||
|
||||
using namespace Constants;
|
||||
terminalWidget->unlockGlobalAction("Coreplugin.OutputPane.minmax");
|
||||
terminalWidget->unlockGlobalAction(Core::Constants::LOCATE);
|
||||
terminalWidget->unlockGlobalAction(NEWTERMINAL);
|
||||
terminalWidget->unlockGlobalAction(NEXTTERMINAL);
|
||||
terminalWidget->unlockGlobalAction(PREVTERMINAL);
|
||||
|
||||
m_tabWidget.setCurrentIndex(m_tabWidget.addTab(terminalWidget, Tr::tr("Terminal")));
|
||||
setupTerminalWidget(terminalWidget);
|
||||
|
||||
@@ -229,6 +241,23 @@ void TerminalPane::initActions()
|
||||
{
|
||||
createShellMenu();
|
||||
|
||||
lockKeyboard.setCheckable(true);
|
||||
lockKeyboard.setChecked(TerminalSettings::instance().lockKeyboard());
|
||||
|
||||
auto updateLockKeyboard = [this](bool locked) {
|
||||
TerminalSettings::instance().lockKeyboard.setValue(locked);
|
||||
if (locked) {
|
||||
lockKeyboard.setIcon(Icons::LOCKED_TOOLBAR.icon());
|
||||
lockKeyboard.setToolTip(Tr::tr("Keyboard shortcuts will be send to the Terminal"));
|
||||
} else {
|
||||
lockKeyboard.setIcon(Icons::UNLOCKED_TOOLBAR.icon());
|
||||
lockKeyboard.setToolTip(Tr::tr("Keyboard shortcuts will be send to Qt Creator"));
|
||||
}
|
||||
};
|
||||
|
||||
updateLockKeyboard(TerminalSettings::instance().lockKeyboard());
|
||||
connect(&lockKeyboard, &QAction::toggled, this, updateLockKeyboard);
|
||||
|
||||
newTerminal.setText(Tr::tr("New Terminal"));
|
||||
newTerminal.setIcon(NEW_TERMINAL_ICON.icon());
|
||||
newTerminal.setToolTip(Tr::tr("Create a new Terminal."));
|
||||
@@ -242,25 +271,22 @@ void TerminalPane::initActions()
|
||||
|
||||
using namespace Constants;
|
||||
|
||||
ActionManager::registerAction(&newTerminal, NEWTERMINAL, m_context)
|
||||
->setDefaultKeySequences({QKeySequence(
|
||||
HostOsInfo::isMacHost() ? QLatin1String("Ctrl+T") : QLatin1String("Ctrl+Shift+T"))});
|
||||
Command *cmd = ActionManager::registerAction(&newTerminal, NEWTERMINAL, m_selfContext);
|
||||
cmd->setDefaultKeySequences({QKeySequence(
|
||||
HostOsInfo::isMacHost() ? QLatin1String("Ctrl+T") : QLatin1String("Ctrl+Shift+T"))});
|
||||
|
||||
ActionManager::registerAction(&nextTerminal, NEXTTERMINAL, m_context)
|
||||
ActionManager::registerAction(&nextTerminal, NEXTTERMINAL, m_selfContext)
|
||||
->setDefaultKeySequences(
|
||||
{QKeySequence("Alt+Tab"),
|
||||
QKeySequence(HostOsInfo::isMacHost() ? QLatin1String("Ctrl+Shift+[")
|
||||
: QLatin1String("Ctrl+PgUp"))});
|
||||
|
||||
ActionManager::registerAction(&prevTerminal, PREVTERMINAL, m_context)
|
||||
ActionManager::registerAction(&prevTerminal, PREVTERMINAL, m_selfContext)
|
||||
->setDefaultKeySequences(
|
||||
{QKeySequence("Alt+Shift+Tab"),
|
||||
QKeySequence(HostOsInfo::isMacHost() ? QLatin1String("Ctrl+Shift+]")
|
||||
: QLatin1String("Ctrl+PgDown"))});
|
||||
|
||||
m_minMax = TerminalWidget::unlockGlobalAction("Coreplugin.OutputPane.minmax", m_context);
|
||||
m_locate = TerminalWidget::unlockGlobalAction(Core::Constants::LOCATE, m_context);
|
||||
|
||||
connect(&newTerminal, &QAction::triggered, this, [this] { openTerminal({}); });
|
||||
connect(&closeTerminal, &QAction::triggered, this, [this] {
|
||||
removeTab(m_tabWidget.currentIndex());
|
||||
@@ -303,10 +329,11 @@ void TerminalPane::createShellMenu()
|
||||
QList<QWidget *> TerminalPane::toolBarWidgets() const
|
||||
{
|
||||
QList<QWidget *> widgets = IOutputPane::toolBarWidgets();
|
||||
|
||||
widgets.prepend(m_newTerminalButton);
|
||||
widgets.prepend(m_closeTerminalButton);
|
||||
|
||||
return widgets << m_openSettingsButton << m_escSettingButton;
|
||||
return widgets << m_openSettingsButton << m_lockKeyboardButton << m_escSettingButton;
|
||||
}
|
||||
|
||||
QString TerminalPane::displayName() const
|
||||
|
@@ -62,18 +62,17 @@ private:
|
||||
QToolButton *m_closeTerminalButton{nullptr};
|
||||
QToolButton *m_openSettingsButton{nullptr};
|
||||
QToolButton *m_escSettingButton{nullptr};
|
||||
|
||||
UnlockedGlobalAction m_minMax;
|
||||
UnlockedGlobalAction m_locate;
|
||||
QToolButton *m_lockKeyboardButton{nullptr};
|
||||
|
||||
QAction newTerminal;
|
||||
QAction nextTerminal;
|
||||
QAction prevTerminal;
|
||||
QAction closeTerminal;
|
||||
QAction lockKeyboard;
|
||||
|
||||
QMenu m_shellMenu;
|
||||
|
||||
Core::Context m_context;
|
||||
Core::Context m_selfContext;
|
||||
|
||||
bool m_widgetInitialized{false};
|
||||
bool m_isVisible{false};
|
||||
|
@@ -32,6 +32,7 @@ public:
|
||||
|
||||
Utils::BoolAspect sendEscapeToTerminal{this};
|
||||
Utils::BoolAspect audibleBell{this};
|
||||
Utils::BoolAspect lockKeyboard{this};
|
||||
};
|
||||
|
||||
} // Terminal
|
||||
|
@@ -252,10 +252,37 @@ void TerminalWidget::setupColors()
|
||||
update();
|
||||
}
|
||||
|
||||
static RegisteredAction registerAction(Id commandId, const Context &context)
|
||||
static bool contextMatcher(QObject *, Qt::ShortcutContext)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
void TerminalWidget::registerShortcut(Command *cmd)
|
||||
{
|
||||
QTC_ASSERT(cmd, return);
|
||||
auto addShortCut = [this, cmd] {
|
||||
for (const auto &keySequence : cmd->keySequences()) {
|
||||
m_shortcutMap.addShortcut(cmd->action(),
|
||||
keySequence,
|
||||
Qt::ShortcutContext::WindowShortcut,
|
||||
contextMatcher);
|
||||
}
|
||||
};
|
||||
auto removeShortCut = [this, cmd] { m_shortcutMap.removeShortcut(0, cmd->action()); };
|
||||
addShortCut();
|
||||
|
||||
connect(cmd, &Command::keySequenceChanged, this, [addShortCut, removeShortCut]() {
|
||||
removeShortCut();
|
||||
addShortCut();
|
||||
});
|
||||
}
|
||||
|
||||
RegisteredAction TerminalWidget::registerAction(Id commandId, const Context &context)
|
||||
{
|
||||
QAction *action = new QAction;
|
||||
ActionManager::registerAction(action, commandId, context);
|
||||
Command *cmd = ActionManager::registerAction(action, commandId, context);
|
||||
|
||||
registerShortcut(cmd);
|
||||
|
||||
return RegisteredAction(action, [commandId](QAction *a) {
|
||||
ActionManager::unregisterAction(a, commandId);
|
||||
@@ -287,10 +314,10 @@ void TerminalWidget::setupActions()
|
||||
this,
|
||||
&TerminalWidget::moveCursorWordRight);
|
||||
|
||||
m_exit = unlockGlobalAction(Core::Constants::EXIT, m_context);
|
||||
m_options = unlockGlobalAction(Core::Constants::OPTIONS, m_context);
|
||||
m_settings = unlockGlobalAction("Preferences.Terminal.General", m_context);
|
||||
m_findInDocument = unlockGlobalAction(Core::Constants::FIND_IN_DOCUMENT, m_context);
|
||||
unlockGlobalAction(Core::Constants::EXIT);
|
||||
unlockGlobalAction(Core::Constants::OPTIONS);
|
||||
unlockGlobalAction("Preferences.Terminal.General");
|
||||
unlockGlobalAction(Core::Constants::FIND_IN_DOCUMENT);
|
||||
}
|
||||
|
||||
void TerminalWidget::closeTerminal()
|
||||
@@ -1506,6 +1533,11 @@ void TerminalWidget::showEvent(QShowEvent *event)
|
||||
|
||||
bool TerminalWidget::event(QEvent *event)
|
||||
{
|
||||
if (TerminalSettings::instance().lockKeyboard() && event->type() == QEvent::ShortcutOverride) {
|
||||
event->accept();
|
||||
return true;
|
||||
}
|
||||
|
||||
if (event->type() == QEvent::Paint) {
|
||||
QPainter p(this);
|
||||
p.fillRect(QRect(QPoint(0, 0), size()), m_currentColors[ColorIndex::Background]);
|
||||
@@ -1513,12 +1545,16 @@ bool TerminalWidget::event(QEvent *event)
|
||||
}
|
||||
|
||||
if (event->type() == QEvent::KeyPress) {
|
||||
QKeyEvent *k = (QKeyEvent *) event;
|
||||
auto k = static_cast<QKeyEvent *>(event);
|
||||
|
||||
if (TerminalSettings::instance().lockKeyboard() && m_shortcutMap.tryShortcut(k))
|
||||
return true;
|
||||
|
||||
keyPressEvent(k);
|
||||
return true;
|
||||
}
|
||||
if (event->type() == QEvent::KeyRelease) {
|
||||
QKeyEvent *k = (QKeyEvent *) event;
|
||||
auto k = static_cast<QKeyEvent *>(event);
|
||||
keyReleaseEvent(k);
|
||||
return true;
|
||||
}
|
||||
@@ -1565,21 +1601,11 @@ void TerminalWidget::initActions()
|
||||
ActionManager::registerAction(&clearTerminal, Constants::CLEAR_TERMINAL, context);
|
||||
}
|
||||
|
||||
UnlockedGlobalAction TerminalWidget::unlockGlobalAction(const Utils::Id &commandId,
|
||||
const Context &context)
|
||||
void TerminalWidget::unlockGlobalAction(const Utils::Id &commandId)
|
||||
{
|
||||
QAction *srcAction = ActionManager::command(commandId)->actionForContext(
|
||||
Core::Constants::C_GLOBAL);
|
||||
|
||||
ProxyAction *proxy = ProxyAction::proxyActionWithIcon(srcAction, srcAction->icon());
|
||||
ActionManager::registerAction(proxy, commandId, context);
|
||||
|
||||
UnlockedGlobalAction registeredAction(proxy, [commandId](QAction *a) {
|
||||
ActionManager::unregisterAction(a, commandId);
|
||||
delete a;
|
||||
});
|
||||
|
||||
return registeredAction;
|
||||
Command *cmd = ActionManager::command(commandId);
|
||||
QTC_ASSERT(cmd, return);
|
||||
registerShortcut(cmd);
|
||||
}
|
||||
|
||||
} // namespace Terminal
|
||||
|
@@ -3,12 +3,14 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "shortcutmap.h"
|
||||
#include "terminalsearch.h"
|
||||
#include "terminalsurface.h"
|
||||
|
||||
#include <aggregation/aggregate.h>
|
||||
|
||||
#include <coreplugin/icontext.h>
|
||||
#include <coreplugin/actionmanager/command.h>
|
||||
|
||||
#include <utils/link.h>
|
||||
#include <utils/process.h>
|
||||
@@ -24,7 +26,6 @@
|
||||
|
||||
namespace Terminal {
|
||||
|
||||
using UnlockedGlobalAction = std::unique_ptr<QAction, std::function<void(QAction *)>>;
|
||||
using RegisteredAction = std::unique_ptr<QAction, std::function<void(QAction *)>>;
|
||||
|
||||
class TerminalWidget : public QAbstractScrollArea
|
||||
@@ -91,8 +92,7 @@ public:
|
||||
|
||||
static void initActions();
|
||||
|
||||
[[nodiscard]] static UnlockedGlobalAction unlockGlobalAction(const Utils::Id &commandId,
|
||||
const Core::Context &context);
|
||||
void unlockGlobalAction(const Utils::Id &commandId);
|
||||
|
||||
signals:
|
||||
void started(qint64 pid);
|
||||
@@ -189,6 +189,9 @@ protected:
|
||||
|
||||
void updateCopyState();
|
||||
|
||||
RegisteredAction registerAction(Utils::Id commandId, const Core::Context &context);
|
||||
void registerShortcut(Core::Command *command);
|
||||
|
||||
private:
|
||||
Core::Context m_context;
|
||||
std::unique_ptr<Utils::Process> m_process;
|
||||
@@ -248,10 +251,7 @@ private:
|
||||
RegisteredAction m_moveCursorWordRight;
|
||||
RegisteredAction m_close;
|
||||
|
||||
UnlockedGlobalAction m_findInDocument;
|
||||
UnlockedGlobalAction m_exit;
|
||||
UnlockedGlobalAction m_options;
|
||||
UnlockedGlobalAction m_settings;
|
||||
Internal::ShortcutMap m_shortcutMap;
|
||||
};
|
||||
|
||||
} // namespace Terminal
|
||||
|
Reference in New Issue
Block a user