vnote/src/vcaptain.cpp

290 lines
7.2 KiB
C++

#include <QtWidgets>
#include <QString>
#include <QDebug>
#include <QShortcut>
#include "vcaptain.h"
#include "veditarea.h"
#include "vedittab.h"
#include "vfilelist.h"
#include "vnavigationmode.h"
#include "vconfigmanager.h"
extern VConfigManager *g_config;
VCaptain::VCaptain(QWidget *p_parent)
: QWidget(p_parent),
m_mode(CaptainMode::Normal),
m_widgetBeforeCaptain(NULL),
m_nextMajorKey('a'),
m_ignoreFocusChange(false)
{
connect(qApp, &QApplication::focusChanged,
this, &VCaptain::handleFocusChanged);
setWindowFlags(Qt::FramelessWindowHint);
// Make it as small as possible. This widget will stay at the top-left corner
// of VMainWindow.
resize(1, 1);
// Register Captain mode leader key.
// This can fix the Input Method blocking issue.
m_captainModeShortcut = new QShortcut(QKeySequence(g_config->getShortcutKeySequence("CaptainMode")),
this);
m_captainModeShortcut->setContext(Qt::ApplicationShortcut);
connect(m_captainModeShortcut, &QShortcut::activated,
this, &VCaptain::trigger);
// Register Navigation mode as Captain mode target.
registerCaptainTarget(tr("NavigationMode"),
g_config->getCaptainShortcutKeySequence("NavigationMode"),
this,
navigationModeByCaptain);
}
QChar VCaptain::getNextMajorKey()
{
QChar ret = m_nextMajorKey;
if (m_nextMajorKey == 'z') {
m_nextMajorKey = QChar();
} else if (!m_nextMajorKey.isNull()) {
m_nextMajorKey = QChar(m_nextMajorKey.toLatin1() + 1);
}
return ret;
}
void VCaptain::registerNavigationTarget(VNavigationMode *p_target)
{
QChar key = getNextMajorKey();
if (!key.isNull()) {
p_target->registerNavigation(key);
m_naviTargets.push_back(NaviModeTarget(p_target, true));
}
}
void VCaptain::handleFocusChanged(QWidget *p_old, QWidget * p_now)
{
Q_UNUSED(p_now);
if (p_old == this
&& !m_ignoreFocusChange
&& !checkMode(CaptainMode::Normal)) {
exitCaptainMode();
}
}
void VCaptain::trigger()
{
if (!checkMode(CaptainMode::Normal)) {
exitCaptainMode();
return;
}
triggerCaptainMode();
}
void VCaptain::keyPressEvent(QKeyEvent *p_event)
{
int key = p_event->key();
Qt::KeyboardModifiers modifiers = p_event->modifiers();
Q_ASSERT(!checkMode(CaptainMode::Normal));
if (VUtils::isMetaKey(key)) {
QWidget::keyPressEvent(p_event);
return;
}
if (handleKeyPress(key, modifiers)) {
p_event->accept();
} else {
QWidget::keyPressEvent(p_event);
}
}
bool VCaptain::handleKeyPress(int p_key, Qt::KeyboardModifiers p_modifiers)
{
bool ret = true;
m_ignoreFocusChange = true;
if (checkMode(CaptainMode::Navigation)) {
ret = handleKeyPressNavigationMode(p_key, p_modifiers);
m_ignoreFocusChange = false;
return ret;
}
ret = handleKeyPressCaptainMode(p_key, p_modifiers);
m_ignoreFocusChange = false;
return ret;
}
bool VCaptain::handleKeyPressNavigationMode(int p_key,
Qt::KeyboardModifiers /* p_modifiers */)
{
Q_ASSERT(m_mode == CaptainMode::Navigation);
bool hasConsumed = false;
bool pending = false;
for (auto &target : m_naviTargets) {
if (hasConsumed) {
target.m_available = false;
target.m_target->hideNavigation();
continue;
}
if (target.m_available) {
bool succeed = false;
// May change focus, so we need to ignore focus change here.
bool consumed = target.m_target->handleKeyNavigation(p_key, succeed);
if (consumed) {
hasConsumed = true;
if (succeed) {
// Exit.
m_widgetBeforeCaptain = NULL;
} else {
// Consumed but not succeed. Need more keys.
pending = true;
}
} else {
// Do not ask this target any more.
target.m_available = false;
target.m_target->hideNavigation();
}
}
}
if (pending) {
return true;
}
exitCaptainMode();
return true;
}
void VCaptain::triggerNavigationMode()
{
setMode(CaptainMode::Navigation);
for (auto &target : m_naviTargets) {
target.m_available = true;
target.m_target->showNavigation();
}
}
void VCaptain::exitNavigationMode()
{
setMode(CaptainMode::Normal);
for (auto &target : m_naviTargets) {
target.m_available = true;
target.m_target->hideNavigation();
}
}
void VCaptain::restoreFocus()
{
if (m_widgetBeforeCaptain) {
m_widgetBeforeCaptain->setFocus();
m_widgetBeforeCaptain = NULL;
}
}
void VCaptain::exitCaptainMode()
{
if (checkMode(CaptainMode::Normal)) {
return;
} else if (checkMode(CaptainMode::Navigation)) {
exitNavigationMode();
}
setMode(CaptainMode::Normal);
m_ignoreFocusChange = false;
restoreFocus();
emit captainModeChanged(false);
}
bool VCaptain::registerCaptainTarget(const QString &p_name,
const QString &p_key,
void *p_target,
CaptainFunc p_func)
{
if (p_key.isEmpty()) {
return false;
}
QString normKey = QKeySequence(p_key).toString();
if (m_captainTargets.contains(normKey)) {
return false;
}
CaptainModeTarget target(p_name,
normKey,
p_target,
p_func);
m_captainTargets.insert(normKey, target);
qDebug() << "registered:" << target.toString() << normKey;
return true;
}
void VCaptain::triggerCaptainTarget(const QString &p_key)
{
auto it = m_captainTargets.find(p_key);
if (it == m_captainTargets.end()) {
return;
}
const CaptainModeTarget &target = it.value();
qDebug() << "triggered:" << target.toString();
CaptainData data(m_widgetBeforeCaptain);
if (!target.m_function(target.m_target, (void *)&data)) {
m_widgetBeforeCaptain = NULL;
}
}
bool VCaptain::navigationModeByCaptain(void *p_target, void *p_data)
{
Q_UNUSED(p_data);
VCaptain *obj = static_cast<VCaptain *>(p_target);
obj->triggerNavigationMode();
return true;
}
void VCaptain::triggerCaptainMode()
{
m_widgetBeforeCaptain = QApplication::focusWidget();
m_ignoreFocusChange = false;
setMode(CaptainMode::Pending);
emit captainModeChanged(true);
// Focus to listen pending key press.
setFocus();
}
bool VCaptain::handleKeyPressCaptainMode(int p_key,
Qt::KeyboardModifiers p_modifiers)
{
Q_ASSERT(checkMode(CaptainMode::Pending));
QString normKey = QKeySequence(p_key | p_modifiers).toString();
triggerCaptainTarget(normKey);
if (!checkMode(CaptainMode::Navigation)) {
exitCaptainMode();
}
return true;
}
void VCaptain::setCaptainModeEnabled(bool p_enabled)
{
m_captainModeShortcut->setEnabled(p_enabled);
}