Revision control
Copy as Markdown
// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
// Qt-Security score:significant reason:default
#include <QtVirtualKeyboard/private/shifthandler_p.h>
#include <QtVirtualKeyboard/private/qvirtualkeyboardinputcontext_p.h>
#include <QtVirtualKeyboard/qvirtualkeyboardinputcontext.h>
#include <QtVirtualKeyboard/qvirtualkeyboardinputengine.h>
#include <QtCore/private/qobject_p.h>
#include <QSet>
#include <QGuiApplication>
#include <QElapsedTimer>
#include <QStyleHints>
#include <QtCore/q20algorithm.h>
QT_BEGIN_NAMESPACE
using namespace Qt::StringLiterals;
namespace QtVirtualKeyboard {
template <typename T, size_t N>
class SmallEnumSet
{
Q_DISABLE_COPY_MOVE(SmallEnumSet)
std::array<T, N> m_data;
public:
// Need a ctor: CTAD doesn't work on aggregates in C++17:
constexpr SmallEnumSet(std::array<T, N> data) : m_data{data} {}
constexpr bool contains(T t) const
{
return std::find(m_data.begin(), m_data.end(), t) != m_data.end();
}
constexpr bool isSorted() const
{
return q20::is_sorted(m_data.begin(), m_data.end());
}
};
constexpr SmallEnumSet manualShiftLanguageFilter = std::array{
QLocale::Arabic,
QLocale::Hindi,
QLocale::Korean,
QLocale::Persian,
QLocale::Thai,
};
static_assert(manualShiftLanguageFilter.isSorted(), "just in case");
constexpr SmallEnumSet manualCapsInputModeFilter = std::array{
QVirtualKeyboardInputEngine::InputMode::Cangjie,
QVirtualKeyboardInputEngine::InputMode::Zhuyin,
QVirtualKeyboardInputEngine::InputMode::Hebrew,
};
static_assert(manualCapsInputModeFilter.isSorted(), "just in case");
constexpr SmallEnumSet noAutoUppercaseInputModeFilter = std::array{
QVirtualKeyboardInputEngine::InputMode::Pinyin,
QVirtualKeyboardInputEngine::InputMode::Cangjie,
QVirtualKeyboardInputEngine::InputMode::Zhuyin,
QVirtualKeyboardInputEngine::InputMode::FullwidthLatin,
QVirtualKeyboardInputEngine::InputMode::ChineseHandwriting,
QVirtualKeyboardInputEngine::InputMode::JapaneseHandwriting,
QVirtualKeyboardInputEngine::InputMode::KoreanHandwriting,
QVirtualKeyboardInputEngine::InputMode::Romaji,
};
static_assert(noAutoUppercaseInputModeFilter.isSorted(), "just in case");
constexpr SmallEnumSet allCapsInputModeFilter = std::array{
QVirtualKeyboardInputEngine::InputMode::Hiragana,
QVirtualKeyboardInputEngine::InputMode::Katakana,
QVirtualKeyboardInputEngine::InputMode::HiraganaFlick,
};
static_assert(allCapsInputModeFilter.isSorted(), "just in case");
class ShiftHandlerPrivate : public QObjectPrivate
{
public:
ShiftHandlerPrivate() :
QObjectPrivate(),
inputContext(nullptr),
sentenceEndingCharacters(u".!?¡¿"_s),
autoCapitalizationEnabled(false),
toggleShiftEnabled(false),
shift(false),
shiftChanged(false),
shiftBeforeCapsLock(false),
capsLock(false),
resetWhenVisible(false)
{
}
QVirtualKeyboardInputContext *inputContext;
QString sentenceEndingCharacters;
bool autoCapitalizationEnabled;
bool toggleShiftEnabled;
bool shift;
bool shiftChanged;
bool shiftBeforeCapsLock;
bool capsLock;
bool resetWhenVisible;
QLocale locale;
QElapsedTimer timer;
};
/*!
\qmltype ShiftHandler
\inqmlmodule QtQuick.VirtualKeyboard
\ingroup qtvirtualkeyboard-internal-qml
\nativetype QtVirtualKeyboard::ShiftHandler
\brief Manages the shift state.
*/
/*!
\class QtVirtualKeyboard::ShiftHandler
\internal
\inmodule QtVirtualKeyboard
\brief Manages the shift state.
*/
ShiftHandler::ShiftHandler(QVirtualKeyboardInputContext *parent) :
QObject(*new ShiftHandlerPrivate(), parent)
{
Q_D(ShiftHandler);
d->inputContext = parent;
}
void ShiftHandler::init()
{
Q_D(ShiftHandler);
connect(d->inputContext, SIGNAL(inputMethodHintsChanged()), SLOT(restart()));
connect(d->inputContext->priv(), SIGNAL(inputItemChanged()), SLOT(restart()));
connect(d->inputContext->inputEngine(), SIGNAL(inputModeChanged()), SLOT(restart()));
connect(d->inputContext, SIGNAL(preeditTextChanged()), SLOT(autoCapitalize()));
connect(d->inputContext, SIGNAL(surroundingTextChanged()), SLOT(autoCapitalize()));
connect(d->inputContext, SIGNAL(cursorPositionChanged()), SLOT(autoCapitalize()));
connect(d->inputContext, SIGNAL(localeChanged()), SLOT(localeChanged()));
connect(qGuiApp->inputMethod(), SIGNAL(visibleChanged()), SLOT(inputMethodVisibleChanged()));
d->locale = QLocale(d->inputContext->locale());
}
/*!
\internal
*/
ShiftHandler::~ShiftHandler()
{
}
QString ShiftHandler::sentenceEndingCharacters() const
{
Q_D(const ShiftHandler);
return d->sentenceEndingCharacters;
}
void ShiftHandler::setSentenceEndingCharacters(const QString &value)
{
Q_D(ShiftHandler);
if (d->sentenceEndingCharacters != value) {
d->sentenceEndingCharacters = value;
autoCapitalize();
emit sentenceEndingCharactersChanged();
}
}
bool ShiftHandler::isAutoCapitalizationEnabled() const
{
Q_D(const ShiftHandler);
return d->autoCapitalizationEnabled;
}
bool ShiftHandler::isToggleShiftEnabled() const
{
Q_D(const ShiftHandler);
return d->toggleShiftEnabled;
}
bool ShiftHandler::isShiftActive() const
{
Q_D(const ShiftHandler);
return d->shift;
}
void ShiftHandler::setShiftActive(bool active)
{
Q_D(ShiftHandler);
if (d->shift != active) {
d->shift = active;
d->shiftChanged = true;
emit shiftActiveChanged();
if (!d->capsLock)
emit uppercaseChanged();
}
}
bool ShiftHandler::isCapsLockActive() const
{
Q_D(const ShiftHandler);
return d->capsLock;
}
void ShiftHandler::setCapsLockActive(bool active)
{
Q_D(ShiftHandler);
if (d->capsLock != active) {
d->capsLock = active;
emit capsLockActiveChanged();
if (!d->shift)
emit uppercaseChanged();
}
}
bool ShiftHandler::isUppercase() const
{
Q_D(const ShiftHandler);
return d->shift || d->capsLock;
}
/*!
\since 1.2
\qmlmethod void ShiftHandler::toggleShift()
Toggles the current shift state.
This method provides the functionality of the shift key.
\sa toggleShiftEnabled
*/
/*!
\since 1.2
\internal
\fn void QtVirtualKeyboard::ShiftHandler::toggleShift()
Toggles the current shift state.
This method provides the functionality of the shift key.
\sa toggleShiftEnabled
*/
void ShiftHandler::toggleShift()
{
Q_D(ShiftHandler);
if (!d->toggleShiftEnabled)
return;
if (manualShiftLanguageFilter.contains(d->locale.language())) {
setCapsLockActive(false);
setShiftActive(!d->shift);
} else if (manualCapsInputModeFilter.contains(d->inputContext->inputEngine()->inputMode())) {
bool capsLock = d->capsLock;
setCapsLockActive(!capsLock);
setShiftActive(!capsLock);
} else if (d->capsLock) {
setCapsLockActive(!d->capsLock && d->shift && !d->shiftChanged);
setShiftActive(d->shiftBeforeCapsLock);
d->shiftChanged = false;
} else {
QStyleHints *style = QGuiApplication::styleHints();
if (!d->timer.isValid() || d->timer.elapsed() > style->mouseDoubleClickInterval()) {
d->timer.start();
d->shiftBeforeCapsLock = d->shift;
} else if (d->timer.elapsed() < style->mouseDoubleClickInterval() && !d->capsLock) {
setCapsLockActive(!d->capsLock);
}
setShiftActive(d->capsLock || !d->shift);
d->shiftChanged = false;
}
}
/*! Clears the toggle shift timer.
\internal
*/
void ShiftHandler::clearToggleShiftTimer()
{
Q_D(ShiftHandler);
d->timer.invalidate();
}
void ShiftHandler::reset()
{
Q_D(ShiftHandler);
if (d->inputContext->priv()->inputItem() || QtVirtualKeyboard::forceEventsWithoutFocus()) {
Qt::InputMethodHints inputMethodHints = d->inputContext->inputMethodHints();
QVirtualKeyboardInputEngine::InputMode inputMode = d->inputContext->inputEngine()->inputMode();
bool preferUpperCase = (inputMethodHints & (Qt::ImhPreferUppercase | Qt::ImhUppercaseOnly));
bool autoCapitalizationEnabled = !(d->inputContext->inputMethodHints() & (Qt::ImhNoAutoUppercase |
Qt::ImhUppercaseOnly | Qt::ImhLowercaseOnly | Qt::ImhEmailCharactersOnly |
Qt::ImhUrlCharactersOnly | Qt::ImhDialableCharactersOnly | Qt::ImhFormattedNumbersOnly |
Qt::ImhDigitsOnly)) && !noAutoUppercaseInputModeFilter.contains(inputMode);
bool toggleShiftEnabled = !(inputMethodHints & (Qt::ImhUppercaseOnly | Qt::ImhLowercaseOnly));
// For filtered languages reset the initial shift status to lower case
// and allow manual shift change
if (manualShiftLanguageFilter.contains(d->locale.language()) ||
manualCapsInputModeFilter.contains(inputMode)) {
preferUpperCase = false;
autoCapitalizationEnabled = false;
toggleShiftEnabled = true;
} else if (allCapsInputModeFilter.contains(inputMode)) {
preferUpperCase = true;
autoCapitalizationEnabled = false;
toggleShiftEnabled = false;
}
setToggleShiftEnabled(toggleShiftEnabled);
setAutoCapitalizationEnabled(autoCapitalizationEnabled);
setCapsLockActive(preferUpperCase);
if (preferUpperCase)
setShiftActive(preferUpperCase);
else
autoCapitalize();
}
}
void ShiftHandler::autoCapitalize()
{
Q_D(ShiftHandler);
if (d->capsLock)
return;
if (!d->autoCapitalizationEnabled || !d->inputContext->preeditText().isEmpty()) {
setShiftActive(false);
} else {
int cursorPosition = d->inputContext->cursorPosition();
bool preferLowerCase = d->inputContext->inputMethodHints() & Qt::ImhPreferLowercase;
if (cursorPosition == 0) {
setShiftActive(!preferLowerCase);
} else { // space after sentence-ending character triggers auto-capitalization
QString text = d->inputContext->surroundingText();
text.truncate(cursorPosition);
if (text.trimmed().size() == 0)
setShiftActive(!preferLowerCase);
else if (text.endsWith(QLatin1Char(' ')))
setShiftActive(d->sentenceEndingCharacters.contains(QStringView{text}.right(2)[0])
&& !preferLowerCase);
else
setShiftActive(false);
}
}
// This will prevent unwanted capsLock-enable
if (d->timer.isValid())
d->timer.invalidate();
}
void ShiftHandler::restart()
{
Q_D(ShiftHandler);
const QGuiApplication *app = qGuiApp;
if (!app || !app->inputMethod()->isVisible()) {
d->resetWhenVisible = true;
return;
}
reset();
}
void ShiftHandler::localeChanged()
{
Q_D(ShiftHandler);
d->locale = QLocale(d->inputContext->locale());
restart();
}
void ShiftHandler::inputMethodVisibleChanged()
{
Q_D(ShiftHandler);
if (!d->resetWhenVisible)
return;
const QGuiApplication *app = qGuiApp;
if (app && app->inputMethod()->isVisible()) {
d->resetWhenVisible = false;
reset();
}
}
void ShiftHandler::setAutoCapitalizationEnabled(bool enabled)
{
Q_D(ShiftHandler);
if (d->autoCapitalizationEnabled != enabled) {
d->autoCapitalizationEnabled = enabled;
emit autoCapitalizationEnabledChanged();
}
}
void ShiftHandler::setToggleShiftEnabled(bool enabled)
{
Q_D(ShiftHandler);
if (d->toggleShiftEnabled != enabled) {
d->toggleShiftEnabled = enabled;
emit toggleShiftEnabledChanged();
}
}
/*!
\property QtVirtualKeyboard::ShiftHandler::sentenceEndingCharacters
This property specifies the sentence ending characters which
will cause shift state change.
By default, the property is initialized to sentence
ending characters found in the ASCII range (i.e. ".!?").
*/
/*!
\qmlproperty string ShiftHandler::sentenceEndingCharacters
This property specifies the sentence ending characters which
will cause shift state change.
By default, the property is initialized to sentence
ending characters found in the ASCII range (i.e. ".!?").
*/
/*!
\since 1.2
\property QtVirtualKeyboard::ShiftHandler::autoCapitalizationEnabled
This property provides the current state of the automatic
capitalization feature.
*/
/*!
\since 1.2
\qmlproperty bool ShiftHandler::autoCapitalizationEnabled
This property provides the current state of the automatic
capitalization feature.
*/
/*!
\since 1.2
\property QtVirtualKeyboard::ShiftHandler::toggleShiftEnabled
This property provides the current state of the toggleShift()
method. When true, the current shift state can be changed by
calling the toggleShift() method.
*/
/*!
\since 1.2
\qmlproperty bool ShiftHandler::toggleShiftEnabled
This property provides the current state of the toggleShift()
method. When true, the current shift state can be changed by
calling the toggleShift() method.
*/
} // namespace QtVirtualKeyboard
QT_END_NAMESPACE