1342 lines
36 KiB
C++
1342 lines
36 KiB
C++
/*
|
|
* SPDX-License-Identifier: GPL-3.0-only
|
|
* MuseScore-CLA-applies
|
|
*
|
|
* MuseScore
|
|
* Music Composition & Notation
|
|
*
|
|
* Copyright (C) 2021 MuseScore BVBA and others
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License version 3 as
|
|
* published by the Free Software Foundation.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
*/
|
|
#include "navigationcontroller.h"
|
|
|
|
#include <algorithm>
|
|
#include <limits>
|
|
#include <utility>
|
|
|
|
#include <QApplication>
|
|
#include <QWindow>
|
|
#include <QTextStream>
|
|
|
|
#include "diagnostics/diagnosticutils.h"
|
|
#include "async/async.h"
|
|
#include "defer.h"
|
|
#include "log.h"
|
|
|
|
// #define NAVIGATION_LOGGING_ENABLED
|
|
|
|
#ifdef NAVIGATION_LOGGING_ENABLED
|
|
#define MYLOG() LOGI()
|
|
#else
|
|
#define MYLOG() LOGN()
|
|
#endif
|
|
|
|
using namespace mu::ui;
|
|
|
|
static const mu::UriQuery DEV_SHOW_CONTROLS_URI("musescore://devtools/keynav/controls?sync=false&modal=false");
|
|
|
|
using MoveDirection = NavigationController::MoveDirection;
|
|
using Event = INavigation::Event;
|
|
using ActivationType = INavigation::ActivationType;
|
|
|
|
// algorithms
|
|
template<class T>
|
|
static T* findNearestEnabled(const std::set<T*>& set, const INavigation::Index& currentIndex, MoveDirection direction)
|
|
{
|
|
TRACEFUNC;
|
|
T* ret = nullptr;
|
|
for (T* v : set) {
|
|
if (!v->enabled()) {
|
|
continue;
|
|
}
|
|
|
|
switch (direction) {
|
|
case MoveDirection::First: {
|
|
if (!ret) {
|
|
ret = v;
|
|
continue;
|
|
}
|
|
|
|
if (v->index().row <= ret->index().row) {
|
|
if (v->index().column <= ret->index().column) {
|
|
ret = v;
|
|
continue;
|
|
}
|
|
}
|
|
} break;
|
|
case MoveDirection::Last: {
|
|
if (!ret) {
|
|
ret = v;
|
|
continue;
|
|
}
|
|
|
|
if (v->index().row >= ret->index().row) {
|
|
if (v->index().column >= ret->index().column) {
|
|
ret = v;
|
|
continue;
|
|
}
|
|
}
|
|
} break;
|
|
case MoveDirection::Right: {
|
|
if (v->index().row != currentIndex.row) {
|
|
continue;
|
|
}
|
|
|
|
if (v->index().column > currentIndex.column) {
|
|
if (!ret) {
|
|
ret = v;
|
|
continue;
|
|
}
|
|
|
|
if (v->index().column < ret->index().column) {
|
|
ret = v;
|
|
}
|
|
}
|
|
} break;
|
|
case MoveDirection::Left: {
|
|
if (v->index().row != currentIndex.row) {
|
|
continue;
|
|
}
|
|
|
|
if (v->index().column < currentIndex.column) {
|
|
if (!ret) {
|
|
ret = v;
|
|
continue;
|
|
}
|
|
|
|
if (v->index().column > ret->index().column) {
|
|
ret = v;
|
|
}
|
|
}
|
|
} break;
|
|
case MoveDirection::Down: {
|
|
if (v->index().column != currentIndex.column) {
|
|
continue;
|
|
}
|
|
|
|
if (v->index().row > currentIndex.row) {
|
|
if (!ret) {
|
|
ret = v;
|
|
continue;
|
|
}
|
|
|
|
if (v->index().row < ret->index().row) {
|
|
ret = v;
|
|
}
|
|
}
|
|
} break;
|
|
case MoveDirection::Up: {
|
|
if (v->index().column != currentIndex.column) {
|
|
continue;
|
|
}
|
|
|
|
if (v->index().row < currentIndex.row) {
|
|
if (!ret) {
|
|
ret = v;
|
|
continue;
|
|
}
|
|
|
|
if (v->index().row > ret->index().row) {
|
|
ret = v;
|
|
}
|
|
}
|
|
} break;
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
template<class T>
|
|
static T* firstEnabled(const std::set<T*>& set, const INavigation::Index& currentIndex, MoveDirection direction)
|
|
{
|
|
if (set.empty()) {
|
|
return nullptr;
|
|
}
|
|
|
|
IF_ASSERT_FAILED(direction == MoveDirection::Right || direction == MoveDirection::Down) {
|
|
return nullptr;
|
|
}
|
|
|
|
return findNearestEnabled<T>(set, currentIndex, direction);
|
|
}
|
|
|
|
template<class T>
|
|
static T* firstEnabled(const std::set<T*>& set)
|
|
{
|
|
if (set.empty()) {
|
|
return nullptr;
|
|
}
|
|
|
|
return findNearestEnabled<T>(set, INavigation::Index(), MoveDirection::First);
|
|
}
|
|
|
|
template<class T>
|
|
static T* lastEnabled(const std::set<T*>& set, const INavigation::Index& currentIndex, MoveDirection direction)
|
|
{
|
|
if (set.empty()) {
|
|
return nullptr;
|
|
}
|
|
|
|
IF_ASSERT_FAILED(direction == MoveDirection::Left || (direction == MoveDirection::Up)) {
|
|
return nullptr;
|
|
}
|
|
|
|
return findNearestEnabled<T>(set, currentIndex, direction);
|
|
}
|
|
|
|
template<class T>
|
|
static T* lastEnabled(const std::set<T*>& set)
|
|
{
|
|
if (set.empty()) {
|
|
return nullptr;
|
|
}
|
|
|
|
return findNearestEnabled<T>(set, INavigation::Index(), MoveDirection::Last);
|
|
}
|
|
|
|
template<class T>
|
|
static T* nextEnabled(const std::set<T*>& set, const INavigation::Index& currentIndex,
|
|
MoveDirection direction = MoveDirection::Right)
|
|
{
|
|
if (set.empty()) {
|
|
return nullptr;
|
|
}
|
|
|
|
return findNearestEnabled<T>(set, currentIndex, direction);
|
|
}
|
|
|
|
template<class T>
|
|
static T* prevEnabled(const std::set<T*>& set, const INavigation::Index& currentIndex, MoveDirection direction = MoveDirection::Left)
|
|
{
|
|
if (set.empty()) {
|
|
return nullptr;
|
|
}
|
|
|
|
return findNearestEnabled<T>(set, currentIndex, direction);
|
|
}
|
|
|
|
template<class T>
|
|
static T* findActive(const std::set<T*>& set)
|
|
{
|
|
auto it = std::find_if(set.cbegin(), set.cend(), [](const T* s) {
|
|
return s->active();
|
|
});
|
|
|
|
if (it != set.cend()) {
|
|
return *it;
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
template<class T>
|
|
static T* findByName(const std::set<T*>& set, const QString& name)
|
|
{
|
|
auto it = std::find_if(set.cbegin(), set.cend(), [name](const T* s) {
|
|
return s->name() == name;
|
|
});
|
|
|
|
if (it != set.cend()) {
|
|
return *it;
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
template<class T>
|
|
static T* findByIndex(const std::set<T*>& set, const INavigation::Index& idx)
|
|
{
|
|
auto it = std::find_if(set.cbegin(), set.cend(), [idx](const T* s) {
|
|
return s->index() == idx;
|
|
});
|
|
|
|
if (it != set.cend()) {
|
|
return *it;
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
void NavigationController::init()
|
|
{
|
|
dispatcher()->reg(this, "nav-next-section", [this]() { navigateTo(NavigationType::NextSection); });
|
|
dispatcher()->reg(this, "nav-prev-section", [this]() { navigateTo(NavigationType::PrevSection); });
|
|
dispatcher()->reg(this, "nav-next-panel", [this]() { navigateTo(NavigationType::NextPanel); });
|
|
dispatcher()->reg(this, "nav-prev-panel", [this]() { navigateTo(NavigationType::PrevPanel); });
|
|
//! NOTE Same as panel at the moment
|
|
dispatcher()->reg(this, "nav-next-tab", [this]() { navigateTo(NavigationType::NextPanel); });
|
|
dispatcher()->reg(this, "nav-prev-tab", [this]() { navigateTo(NavigationType::PrevPanel); });
|
|
|
|
dispatcher()->reg(this, "nav-trigger-control", [this]() { doTriggerControl(); });
|
|
|
|
dispatcher()->reg(this, "nav-right", [this]() { navigateTo(NavigationType::Right); });
|
|
dispatcher()->reg(this, "nav-left", [this]() { navigateTo(NavigationType::Left); });
|
|
dispatcher()->reg(this, "nav-up", [this]() { navigateTo(NavigationType::Up); });
|
|
dispatcher()->reg(this, "nav-down", [this]() { navigateTo(NavigationType::Down); });
|
|
dispatcher()->reg(this, "nav-escape", [this]() { onEscape(); });
|
|
|
|
dispatcher()->reg(this, "nav-first-control", [this]() { navigateTo(NavigationType::FirstControl); }); // typically Home key
|
|
dispatcher()->reg(this, "nav-last-control", [this]() { navigateTo(NavigationType::LastControl); }); // typically End key
|
|
dispatcher()->reg(this, "nav-nextrow-control", [this]() { navigateTo(NavigationType::NextRowControl); }); // typically PageDown key
|
|
dispatcher()->reg(this, "nav-prevrow-control", [this]() { navigateTo(NavigationType::PrevRowControl); }); // typically PageUp key
|
|
|
|
qApp->installEventFilter(this);
|
|
}
|
|
|
|
void NavigationController::reg(INavigationSection* section)
|
|
{
|
|
//! TODO add check on valid state
|
|
TRACEFUNC;
|
|
m_sections.insert(section);
|
|
section->setOnActiveRequested([this](INavigationSection* section, INavigationPanel* panel, INavigationControl* control,
|
|
bool enableHighlight, ActivationType activationType) {
|
|
if (control && activationType == ActivationType::ByMouse) {
|
|
if (mainWindow()->qWindow() == control->window()) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (enableHighlight) {
|
|
setIsHighlight(true);
|
|
}
|
|
|
|
onActiveRequested(section, panel, control);
|
|
});
|
|
}
|
|
|
|
void NavigationController::unreg(INavigationSection* section)
|
|
{
|
|
TRACEFUNC;
|
|
m_sections.erase(section);
|
|
section->setOnActiveRequested(nullptr);
|
|
}
|
|
|
|
const std::set<INavigationSection*>& NavigationController::sections() const
|
|
{
|
|
return m_sections;
|
|
}
|
|
|
|
bool NavigationController::isHighlight() const
|
|
{
|
|
return m_isHighlight;
|
|
}
|
|
|
|
void NavigationController::setIsHighlight(bool isHighlight)
|
|
{
|
|
if (m_isHighlight == isHighlight) {
|
|
return;
|
|
}
|
|
|
|
m_isHighlight = isHighlight;
|
|
m_highlightChanged.notify();
|
|
}
|
|
|
|
mu::async::Notification NavigationController::highlightChanged() const
|
|
{
|
|
return m_highlightChanged;
|
|
}
|
|
|
|
void NavigationController::setIsResetOnMousePress(bool arg)
|
|
{
|
|
m_isResetOnMousePress = arg;
|
|
}
|
|
|
|
void NavigationController::resetIfNeed(QObject* watched)
|
|
{
|
|
if (!m_isResetOnMousePress) {
|
|
return;
|
|
}
|
|
|
|
#ifdef MUE_BUILD_DIAGNOSTICS_MODULE
|
|
if (diagnostics::isDiagnosticHierarchy(watched)) {
|
|
return;
|
|
}
|
|
#endif
|
|
|
|
auto activeCtrl = activeControl();
|
|
if (activeCtrl && activeCtrl != m_defaultNavigationControl && watched == qApp) {
|
|
resetActive();
|
|
}
|
|
|
|
setIsHighlight(false);
|
|
}
|
|
|
|
bool NavigationController::eventFilter(QObject* watched, QEvent* event)
|
|
{
|
|
if (event->type() == QEvent::MouseButtonPress) {
|
|
resetIfNeed(watched);
|
|
}
|
|
|
|
return QObject::eventFilter(watched, event);
|
|
}
|
|
|
|
void NavigationController::navigateTo(NavigationController::NavigationType type)
|
|
{
|
|
switch (type) {
|
|
case NavigationType::NextSection:
|
|
goToNextSection();
|
|
break;
|
|
case NavigationType::PrevSection:
|
|
goToPrevSection(false);
|
|
break;
|
|
case NavigationType::PrevSectionActiveLastPanel:
|
|
goToPrevSection(true);
|
|
break;
|
|
case NavigationType::NextPanel:
|
|
goToNextPanel();
|
|
break;
|
|
case NavigationType::PrevPanel:
|
|
goToPrevPanel();
|
|
break;
|
|
case NavigationType::Left:
|
|
onLeft();
|
|
break;
|
|
case NavigationType::Right:
|
|
onRight();
|
|
break;
|
|
case NavigationType::Up:
|
|
onUp();
|
|
break;
|
|
case NavigationType::Down:
|
|
onDown();
|
|
break;
|
|
case NavigationType::FirstControl:
|
|
goToFirstControl();
|
|
break;
|
|
case NavigationType::LastControl:
|
|
goToLastControl();
|
|
break;
|
|
case NavigationType::NextRowControl:
|
|
goToNextRowControl();
|
|
break;
|
|
case NavigationType::PrevRowControl:
|
|
goToPrevRowControl();
|
|
break;
|
|
}
|
|
|
|
setIsHighlight(true);
|
|
}
|
|
|
|
void NavigationController::resetActive()
|
|
{
|
|
MYLOG() << "===";
|
|
INavigationSection* activeSec = this->activeSection();
|
|
if (!activeSec) {
|
|
return;
|
|
}
|
|
|
|
INavigationPanel* activePnl = findActive(activeSec->panels());
|
|
if (activePnl) {
|
|
INavigationControl* activeCtrl = findActive(activePnl->controls());
|
|
if (activeCtrl) {
|
|
activeCtrl->setActive(false);
|
|
}
|
|
|
|
activePnl->setActive(false);
|
|
}
|
|
|
|
activeSec->setActive(false);
|
|
|
|
if (m_defaultNavigationControl) {
|
|
INavigationPanel* panel = m_defaultNavigationControl->panel();
|
|
INavigationSection* section = panel ? panel->section() : nullptr;
|
|
if (section->enabled()) {
|
|
m_defaultNavigationControl->setActive(true);
|
|
m_navigationChanged.notify();
|
|
}
|
|
}
|
|
}
|
|
|
|
void NavigationController::doActivateSection(INavigationSection* sect, bool isActivateLastPanel)
|
|
{
|
|
TRACEFUNC;
|
|
IF_ASSERT_FAILED(sect) {
|
|
return;
|
|
}
|
|
|
|
for (INavigationPanel* panel : sect->panels()) {
|
|
doDeactivatePanel(panel);
|
|
}
|
|
|
|
sect->setActive(true);
|
|
MYLOG() << "activated section: " << sect->name() << ", order: " << sect->index().order();
|
|
|
|
INavigationPanel* toActivatePanel = nullptr;
|
|
if (isActivateLastPanel) {
|
|
toActivatePanel = lastEnabled(sect->panels());
|
|
} else {
|
|
toActivatePanel = firstEnabled(sect->panels());
|
|
}
|
|
|
|
if (toActivatePanel) {
|
|
doActivatePanel(toActivatePanel);
|
|
}
|
|
}
|
|
|
|
void NavigationController::doDeactivateSection(INavigationSection* sect)
|
|
{
|
|
TRACEFUNC;
|
|
IF_ASSERT_FAILED(sect) {
|
|
return;
|
|
}
|
|
|
|
for (INavigationPanel* panel : sect->panels()) {
|
|
doDeactivatePanel(panel);
|
|
}
|
|
|
|
sect->setActive(false);
|
|
}
|
|
|
|
void NavigationController::doActivatePanel(INavigationPanel* panel)
|
|
{
|
|
TRACEFUNC;
|
|
IF_ASSERT_FAILED(panel) {
|
|
return;
|
|
}
|
|
|
|
for (INavigationControl* ctrl : panel->controls()) {
|
|
doDeactivateControl(ctrl);
|
|
}
|
|
|
|
INavigation::EventPtr event = INavigation::Event::make(INavigation::Event::AboutActive);
|
|
panel->onEvent(event);
|
|
|
|
panel->setActive(true);
|
|
MYLOG() << "activated panel: " << panel->name() << ", order: " << panel->index().order();
|
|
|
|
INavigationControl* control = nullptr;
|
|
QString ctrlName = event->data.value("controlName").toString();
|
|
if (!ctrlName.isEmpty()) {
|
|
control = findByName(panel->controls(), ctrlName);
|
|
IF_ASSERT_FAILED(control) {
|
|
}
|
|
}
|
|
|
|
if (!control) {
|
|
QVariantList idxVal = event->data.value("controlIndex").toList();
|
|
if (idxVal.count() == 2) {
|
|
INavigation::Index ctrlIndex;
|
|
ctrlIndex.row = idxVal.at(0).toInt();
|
|
ctrlIndex.column = idxVal.at(1).toInt();
|
|
control = findByIndex(panel->controls(), ctrlIndex);
|
|
if (!control) {
|
|
bool isOptional = event->data.value("controlOptional").toBool();
|
|
if (!isOptional) {
|
|
IF_ASSERT_FAILED(control) {
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!control) {
|
|
control = firstEnabled(panel->controls());
|
|
}
|
|
|
|
if (control) {
|
|
doActivateControl(control);
|
|
}
|
|
}
|
|
|
|
void NavigationController::doDeactivatePanel(INavigationPanel* panel)
|
|
{
|
|
TRACEFUNC;
|
|
IF_ASSERT_FAILED(panel) {
|
|
return;
|
|
}
|
|
|
|
for (INavigationControl* ctr : panel->controls()) {
|
|
doDeactivateControl(ctr);
|
|
}
|
|
|
|
if (panel->active()) {
|
|
panel->setActive(false);
|
|
}
|
|
}
|
|
|
|
void NavigationController::doActivateControl(INavigationControl* ctrl)
|
|
{
|
|
TRACEFUNC;
|
|
IF_ASSERT_FAILED(ctrl) {
|
|
return;
|
|
}
|
|
|
|
ctrl->setActive(true);
|
|
MYLOG() << "activated control: " << ctrl->name() << ", row: " << ctrl->index().row << ", column: " << ctrl->index().column;
|
|
}
|
|
|
|
void NavigationController::doDeactivateControl(INavigationControl* ctrl)
|
|
{
|
|
TRACEFUNC;
|
|
IF_ASSERT_FAILED(ctrl) {
|
|
return;
|
|
}
|
|
|
|
if (ctrl->active()) {
|
|
ctrl->setActive(false);
|
|
}
|
|
}
|
|
|
|
void NavigationController::doActivateFirst()
|
|
{
|
|
if (m_sections.empty()) {
|
|
return;
|
|
}
|
|
|
|
INavigationSection* first = firstEnabled(m_sections);
|
|
if (first) {
|
|
doActivateSection(first);
|
|
}
|
|
}
|
|
|
|
void NavigationController::doActivateLast()
|
|
{
|
|
if (m_sections.empty()) {
|
|
return;
|
|
}
|
|
|
|
INavigationSection* last = lastEnabled(m_sections);
|
|
if (last) {
|
|
doActivateSection(last);
|
|
}
|
|
}
|
|
|
|
INavigationSection* NavigationController::activeSection() const
|
|
{
|
|
TRACEFUNC;
|
|
if (m_sections.empty()) {
|
|
return nullptr;
|
|
}
|
|
return findActive(m_sections);
|
|
}
|
|
|
|
INavigationPanel* NavigationController::activePanel() const
|
|
{
|
|
TRACEFUNC;
|
|
INavigationSection* activeSec = activeSection();
|
|
if (!activeSec) {
|
|
return nullptr;
|
|
}
|
|
return findActive(activeSec->panels());
|
|
}
|
|
|
|
INavigationControl* NavigationController::activeControl() const
|
|
{
|
|
TRACEFUNC;
|
|
INavigationPanel* activePanel = this->activePanel();
|
|
if (!activePanel) {
|
|
return nullptr;
|
|
}
|
|
return findActive(activePanel->controls());
|
|
}
|
|
|
|
void NavigationController::setDefaultNavigationControl(INavigationControl* control)
|
|
{
|
|
m_defaultNavigationControl = control;
|
|
}
|
|
|
|
mu::async::Notification NavigationController::navigationChanged() const
|
|
{
|
|
return m_navigationChanged;
|
|
}
|
|
|
|
void NavigationController::goToNextSection()
|
|
{
|
|
TRACEFUNC;
|
|
MYLOG() << "====";
|
|
if (m_sections.empty()) {
|
|
return;
|
|
}
|
|
|
|
INavigationSection* activeSec = findActive(m_sections);
|
|
if (!activeSec) { // no any active
|
|
doActivateFirst();
|
|
return;
|
|
}
|
|
|
|
doDeactivateSection(activeSec);
|
|
|
|
INavigationSection* nextSec = nextEnabled(m_sections, activeSec->index());
|
|
if (!nextSec) { // active is last
|
|
nextSec = firstEnabled(m_sections); // the first to be the next
|
|
}
|
|
|
|
LOGI() << "nextSec: " << nextSec->name() << ", enabled: " << nextSec->enabled();
|
|
|
|
doActivateSection(nextSec);
|
|
|
|
m_navigationChanged.notify();
|
|
}
|
|
|
|
void NavigationController::goToPrevSection(bool isActivateLastPanel)
|
|
{
|
|
TRACEFUNC;
|
|
MYLOG() << "====";
|
|
if (m_sections.empty()) {
|
|
return;
|
|
}
|
|
|
|
INavigationSection* activeSec = findActive(m_sections);
|
|
if (!activeSec) { // no any active
|
|
doActivateLast();
|
|
return;
|
|
}
|
|
|
|
doDeactivateSection(activeSec);
|
|
|
|
INavigationSection* prevSec = prevEnabled(m_sections, activeSec->index());
|
|
if (!prevSec) { // active is first
|
|
prevSec = lastEnabled(m_sections); // the last to be the prev
|
|
}
|
|
|
|
doActivateSection(prevSec, isActivateLastPanel);
|
|
|
|
m_navigationChanged.notify();
|
|
}
|
|
|
|
void NavigationController::goToNextPanel()
|
|
{
|
|
TRACEFUNC;
|
|
MYLOG() << "====";
|
|
INavigationSection* activeSec = activeSection();
|
|
if (!activeSec) {
|
|
doActivateFirst();
|
|
m_navigationChanged.notify();
|
|
return;
|
|
}
|
|
|
|
INavigationPanel* activePanel = findActive(activeSec->panels());
|
|
if (!activePanel) { // no any active
|
|
INavigationPanel* first = firstEnabled(activeSec->panels());
|
|
if (first) {
|
|
doActivatePanel(first);
|
|
m_navigationChanged.notify();
|
|
}
|
|
return;
|
|
}
|
|
|
|
doDeactivatePanel(activePanel);
|
|
|
|
INavigationPanel* nextPanel = nextEnabled(activeSec->panels(), activePanel->index());
|
|
if (nextPanel) {
|
|
doActivatePanel(nextPanel);
|
|
m_navigationChanged.notify();
|
|
return;
|
|
}
|
|
|
|
if (activeSec->type() == INavigationSection::Type::Exclusive) {
|
|
INavigationPanel* first = firstEnabled(activeSec->panels());
|
|
if (first) {
|
|
doActivatePanel(first);
|
|
m_navigationChanged.notify();
|
|
}
|
|
return;
|
|
}
|
|
|
|
// active is last panel, go to first panel in next section
|
|
goToNextSection();
|
|
}
|
|
|
|
void NavigationController::goToPrevPanel()
|
|
{
|
|
TRACEFUNC;
|
|
MYLOG() << "====";
|
|
INavigationSection* activeSec = activeSection();
|
|
if (!activeSec) {
|
|
doActivateLast();
|
|
m_navigationChanged.notify();
|
|
return;
|
|
}
|
|
|
|
INavigationPanel* activePanel = findActive(activeSec->panels());
|
|
if (!activePanel) { // no any active
|
|
INavigationPanel* lastPanel = lastEnabled(activeSec->panels());
|
|
if (lastPanel) {
|
|
doActivatePanel(lastPanel);
|
|
m_navigationChanged.notify();
|
|
}
|
|
return;
|
|
}
|
|
|
|
doDeactivatePanel(activePanel);
|
|
|
|
INavigationPanel* prevPanel = prevEnabled(activeSec->panels(), activePanel->index());
|
|
if (prevPanel) {
|
|
doActivatePanel(prevPanel);
|
|
m_navigationChanged.notify();
|
|
return;
|
|
}
|
|
|
|
if (activeSec->type() == INavigationSection::Type::Exclusive) {
|
|
INavigationPanel* lastPanel = lastEnabled(activeSec->panels());
|
|
if (lastPanel) {
|
|
doActivatePanel(lastPanel);
|
|
m_navigationChanged.notify();
|
|
}
|
|
return;
|
|
}
|
|
|
|
// active is first, go to last panel in prev section
|
|
goToPrevSection(true);
|
|
}
|
|
|
|
void NavigationController::onRight()
|
|
{
|
|
TRACEFUNC;
|
|
INavigationPanel* activePanel = this->activePanel();
|
|
if (!activePanel) {
|
|
return;
|
|
}
|
|
|
|
INavigation::EventPtr e = Event::make(Event::Right);
|
|
activePanel->onEvent(e);
|
|
if (e->accepted) {
|
|
return;
|
|
}
|
|
|
|
INavigationControl* activeCtrl = findActive(activePanel->controls());
|
|
if (activeCtrl) {
|
|
activeCtrl->onEvent(e);
|
|
if (e->accepted) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
goToControl(MoveDirection::Right, activePanel);
|
|
}
|
|
|
|
void NavigationController::onLeft()
|
|
{
|
|
TRACEFUNC;
|
|
INavigationPanel* activePanel = this->activePanel();
|
|
if (!activePanel) {
|
|
return;
|
|
}
|
|
|
|
INavigation::EventPtr e = Event::make(Event::Left);
|
|
activePanel->onEvent(e);
|
|
if (e->accepted) {
|
|
return;
|
|
}
|
|
|
|
INavigationControl* activeCtrl = findActive(activePanel->controls());
|
|
if (activeCtrl) {
|
|
activeCtrl->onEvent(e);
|
|
if (e->accepted) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
goToControl(MoveDirection::Left, activePanel);
|
|
}
|
|
|
|
void NavigationController::onDown()
|
|
{
|
|
TRACEFUNC;
|
|
INavigationPanel* activePanel = this->activePanel();
|
|
if (!activePanel) {
|
|
return;
|
|
}
|
|
|
|
INavigation::EventPtr e = Event::make(Event::Down);
|
|
activePanel->onEvent(e);
|
|
if (e->accepted) {
|
|
return;
|
|
}
|
|
|
|
INavigationControl* activeCtrl = findActive(activePanel->controls());
|
|
if (activeCtrl) {
|
|
activeCtrl->onEvent(e);
|
|
if (e->accepted) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
goToControl(MoveDirection::Down, activePanel);
|
|
}
|
|
|
|
void NavigationController::onUp()
|
|
{
|
|
TRACEFUNC;
|
|
INavigationPanel* activePanel = this->activePanel();
|
|
if (!activePanel) {
|
|
return;
|
|
}
|
|
|
|
INavigation::EventPtr e = Event::make(Event::Up);
|
|
activePanel->onEvent(e);
|
|
if (e->accepted) {
|
|
return;
|
|
}
|
|
|
|
INavigationControl* activeCtrl = findActive(activePanel->controls());
|
|
if (activeCtrl) {
|
|
activeCtrl->onEvent(e);
|
|
if (e->accepted) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
goToControl(MoveDirection::Up, activePanel);
|
|
}
|
|
|
|
void NavigationController::onEscape()
|
|
{
|
|
TRACEFUNC;
|
|
MYLOG() << "====";
|
|
INavigationSection* activeSec = activeSection();
|
|
if (!activeSec) {
|
|
return;
|
|
}
|
|
|
|
INavigation::EventPtr e = Event::make(Event::Escape);
|
|
activeSec->onEvent(e);
|
|
if (e->accepted) {
|
|
return;
|
|
}
|
|
|
|
INavigationPanel* activePanel = findActive(activeSec->panels());
|
|
if (!activePanel) {
|
|
return;
|
|
}
|
|
|
|
activePanel->onEvent(e);
|
|
if (e->accepted) {
|
|
return;
|
|
}
|
|
|
|
INavigationControl* activeCtrl = findActive(activePanel->controls());
|
|
if (!activeCtrl) {
|
|
return;
|
|
}
|
|
|
|
activeCtrl->onEvent(e);
|
|
if (e->accepted) {
|
|
return;
|
|
}
|
|
|
|
activeCtrl->setActive(false);
|
|
|
|
if (m_defaultNavigationControl) {
|
|
doActivateControl(m_defaultNavigationControl);
|
|
}
|
|
}
|
|
|
|
void NavigationController::goToFirstControl()
|
|
{
|
|
MYLOG() << "====";
|
|
goToControl(MoveDirection::First);
|
|
}
|
|
|
|
void NavigationController::goToLastControl()
|
|
{
|
|
MYLOG() << "====";
|
|
goToControl(MoveDirection::Last);
|
|
}
|
|
|
|
void NavigationController::goToNextRowControl()
|
|
{
|
|
TRACEFUNC;
|
|
MYLOG() << "====";
|
|
INavigationPanel* activePanel = this->activePanel();
|
|
if (!activePanel) {
|
|
return;
|
|
}
|
|
|
|
INavigationControl* activeControl = findActive(activePanel->controls());
|
|
if (activeControl) {
|
|
doDeactivateControl(activeControl);
|
|
}
|
|
|
|
INavigationControl* toControl = nullptr;
|
|
if (activeControl) {
|
|
INavigation::Index index = activeControl->index();
|
|
index.column = 0;
|
|
toControl = nextEnabled(activePanel->controls(), index, MoveDirection::Down);
|
|
if (!toControl) { // last row
|
|
toControl = firstEnabled(activePanel->controls());
|
|
}
|
|
} else {
|
|
toControl = firstEnabled(activePanel->controls());
|
|
}
|
|
|
|
if (toControl) {
|
|
doActivateControl(toControl);
|
|
}
|
|
|
|
m_navigationChanged.notify();
|
|
}
|
|
|
|
void NavigationController::goToPrevRowControl()
|
|
{
|
|
TRACEFUNC;
|
|
MYLOG() << "====";
|
|
INavigationPanel* activePanel = this->activePanel();
|
|
if (!activePanel) {
|
|
return;
|
|
}
|
|
|
|
INavigationControl* activeControl = findActive(activePanel->controls());
|
|
if (activeControl) {
|
|
doDeactivateControl(activeControl);
|
|
}
|
|
|
|
INavigationControl* toControl = nullptr;
|
|
if (activeControl) {
|
|
INavigation::Index index = activeControl->index();
|
|
index.column = 0;
|
|
toControl = prevEnabled(activePanel->controls(), index, MoveDirection::Up);
|
|
if (!toControl) { // first row
|
|
toControl = lastEnabled(activePanel->controls());
|
|
}
|
|
} else {
|
|
toControl = lastEnabled(activePanel->controls());
|
|
}
|
|
|
|
if (toControl) {
|
|
doActivateControl(toControl);
|
|
}
|
|
|
|
m_navigationChanged.notify();
|
|
}
|
|
|
|
void NavigationController::goToControl(MoveDirection direction, INavigationPanel* activePanel)
|
|
{
|
|
TRACEFUNC;
|
|
MYLOG() << "direction: " << direction;
|
|
if (!activePanel) {
|
|
activePanel = this->activePanel();
|
|
}
|
|
|
|
if (!activePanel) {
|
|
return;
|
|
}
|
|
|
|
INavigationControl* activeControl = findActive(activePanel->controls());
|
|
INavigationControl* toControl = nullptr;
|
|
|
|
bool tryOtherOrientation = activePanel->direction() == INavigationPanel::Direction::Horizontal
|
|
|| activePanel->direction() == INavigationPanel::Direction::Vertical;
|
|
|
|
switch (direction) {
|
|
case MoveDirection::First: {
|
|
toControl = firstEnabled(activePanel->controls());
|
|
} break;
|
|
case MoveDirection::Last: {
|
|
toControl = lastEnabled(activePanel->controls());
|
|
} break;
|
|
case MoveDirection::Right: {
|
|
if (!activeControl) { // no any active
|
|
toControl = firstEnabled(activePanel->controls(), INavigation::Index(), MoveDirection::Right);
|
|
} else {
|
|
toControl = nextEnabled(activePanel->controls(), activeControl->index(), MoveDirection::Right);
|
|
if (!toControl && tryOtherOrientation) {
|
|
toControl = nextEnabled(activePanel->controls(), activeControl->index(), MoveDirection::Down);
|
|
}
|
|
if (!toControl) { // active is last
|
|
INavigation::Index index = activeControl->index();
|
|
index.column = -1;
|
|
toControl = firstEnabled(activePanel->controls(), index, MoveDirection::Right); // the first to be the next
|
|
|
|
if (!toControl && tryOtherOrientation) {
|
|
index = activeControl->index();
|
|
index.row = -1;
|
|
toControl = nextEnabled(activePanel->controls(), index, MoveDirection::Down);
|
|
}
|
|
}
|
|
}
|
|
} break;
|
|
case MoveDirection::Down: {
|
|
if (!activeControl) { // no any active
|
|
toControl = firstEnabled(activePanel->controls(), INavigation::Index(), MoveDirection::Down);
|
|
} else {
|
|
toControl = nextEnabled(activePanel->controls(), activeControl->index(), MoveDirection::Down);
|
|
if (!toControl && tryOtherOrientation) {
|
|
toControl = nextEnabled(activePanel->controls(), activeControl->index(), MoveDirection::Right);
|
|
}
|
|
if (!toControl) { // active is last
|
|
INavigation::Index index = activeControl->index();
|
|
index.row = -1;
|
|
toControl = firstEnabled(activePanel->controls(), index, MoveDirection::Down); // the first to be the next
|
|
|
|
if (!toControl && tryOtherOrientation) {
|
|
index = activeControl->index();
|
|
index.column = -1;
|
|
toControl = nextEnabled(activePanel->controls(), index, MoveDirection::Right);
|
|
}
|
|
}
|
|
}
|
|
} break;
|
|
case MoveDirection::Left: {
|
|
if (!activeControl) { // no any active
|
|
toControl = lastEnabled(activePanel->controls(), INavigation::Index(), MoveDirection::Left);
|
|
} else {
|
|
toControl = nextEnabled(activePanel->controls(), activeControl->index(), MoveDirection::Left);
|
|
if (!toControl && tryOtherOrientation) {
|
|
toControl = nextEnabled(activePanel->controls(), activeControl->index(), MoveDirection::Up);
|
|
}
|
|
if (!toControl) { // active is first
|
|
INavigation::Index index = activeControl->index();
|
|
index.column = std::numeric_limits<int>::max();
|
|
toControl = lastEnabled(activePanel->controls(), index, MoveDirection::Left); // the last to be the next
|
|
|
|
if (!toControl && tryOtherOrientation) {
|
|
index = activeControl->index();
|
|
index.row = std::numeric_limits<int>::max();
|
|
toControl = nextEnabled(activePanel->controls(), index, MoveDirection::Up);
|
|
}
|
|
}
|
|
}
|
|
} break;
|
|
case MoveDirection::Up: {
|
|
if (!activeControl) { // no any active
|
|
toControl = lastEnabled(activePanel->controls(), INavigation::Index(), MoveDirection::Up);
|
|
} else {
|
|
toControl = nextEnabled(activePanel->controls(), activeControl->index(), MoveDirection::Up);
|
|
if (!toControl && tryOtherOrientation) {
|
|
toControl = nextEnabled(activePanel->controls(), activeControl->index(), MoveDirection::Left);
|
|
}
|
|
if (!toControl) { // active is first
|
|
INavigation::Index index = activeControl->index();
|
|
index.row = std::numeric_limits<int>::max();
|
|
toControl = lastEnabled(activePanel->controls(), index, MoveDirection::Up); // the last to be the next
|
|
|
|
if (!toControl && tryOtherOrientation) {
|
|
index = activeControl->index();
|
|
index.column = std::numeric_limits<int>::max();
|
|
toControl = nextEnabled(activePanel->controls(), index, MoveDirection::Left);
|
|
}
|
|
}
|
|
}
|
|
} break;
|
|
}
|
|
|
|
if (!toControl) {
|
|
return;
|
|
}
|
|
|
|
//! NOTE Maybe just one control (or just one enabled control)
|
|
if (toControl == activeControl) {
|
|
return;
|
|
}
|
|
|
|
if (activeControl) {
|
|
MYLOG() << "current activated control: " << activeControl->name()
|
|
<< ", row: " << activeControl->index().row
|
|
<< ", column: " << activeControl->index().column;
|
|
|
|
doDeactivateControl(activeControl);
|
|
}
|
|
|
|
doActivateControl(toControl);
|
|
|
|
m_navigationChanged.notify();
|
|
}
|
|
|
|
void NavigationController::doTriggerControl()
|
|
{
|
|
TRACEFUNC;
|
|
MYLOG() << "====";
|
|
INavigationPanel* activePanel = this->activePanel();
|
|
if (!activePanel) {
|
|
return;
|
|
}
|
|
|
|
INavigation::EventPtr e = Event::make(Event::Trigger);
|
|
activePanel->onEvent(e);
|
|
if (e->accepted) {
|
|
return;
|
|
}
|
|
|
|
INavigationControl* activeCtrl = findActive(activePanel->controls());
|
|
if (!activeCtrl) {
|
|
return;
|
|
}
|
|
|
|
MYLOG() << "triggered control: " << activeCtrl->name();
|
|
activeCtrl->onEvent(e);
|
|
if (e->accepted) {
|
|
return;
|
|
}
|
|
|
|
activeCtrl->trigger();
|
|
}
|
|
|
|
bool NavigationController::requestActivateByName(const std::string& sectName, const std::string& panelName, const std::string& controlName)
|
|
{
|
|
INavigationSection* section = findByName(m_sections, QString::fromStdString(sectName));
|
|
if (!section) {
|
|
LOGE() << "not found section with name: " << sectName;
|
|
return false;
|
|
}
|
|
|
|
INavigationPanel* panel = findByName(section->panels(), QString::fromStdString(panelName));
|
|
if (!panel) {
|
|
LOGE() << "not found panel with name: " << panelName << ", section: " << sectName;
|
|
return false;
|
|
}
|
|
|
|
INavigationControl* control = findByName(panel->controls(), QString::fromStdString(controlName));
|
|
if (!control) {
|
|
LOGE() << "not found control with name: " << controlName << ", panel: " << panelName << ", section: " << sectName;
|
|
QString has = "has:\n";
|
|
for (const INavigationControl* c : panel->controls()) {
|
|
has += c->name() + "\n";
|
|
}
|
|
LOGI() << has;
|
|
return false;
|
|
}
|
|
|
|
onActiveRequested(section, panel, control, true);
|
|
return true;
|
|
}
|
|
|
|
bool NavigationController::requestActivateByIndex(const std::string& sectName, const std::string& panelName,
|
|
const INavigation::Index& controlIndex)
|
|
{
|
|
INavigationSection* section = findByName(m_sections, QString::fromStdString(sectName));
|
|
if (!section) {
|
|
LOGE() << "not found section with name: " << sectName;
|
|
return false;
|
|
}
|
|
|
|
INavigationPanel* panel = findByName(section->panels(), QString::fromStdString(panelName));
|
|
if (!panel) {
|
|
LOGE() << "not found panel with name: " << panelName << ", section: " << sectName;
|
|
return false;
|
|
}
|
|
|
|
INavigationControl* control = findByIndex(panel->controls(), controlIndex);
|
|
if (!control) {
|
|
LOGE() << "not found control with index: " << controlIndex.to_string() << ", panel: " << panelName << ", section: " << sectName;
|
|
std::string has = "has:\n";
|
|
for (const INavigationControl* c : panel->controls()) {
|
|
has += c->index().to_string() + "\n";
|
|
}
|
|
LOGI() << has;
|
|
return false;
|
|
}
|
|
|
|
onActiveRequested(section, panel, control, true);
|
|
return true;
|
|
}
|
|
|
|
void NavigationController::onActiveRequested(INavigationSection* sect, INavigationPanel* panel, INavigationControl* ctrl, bool force)
|
|
{
|
|
TRACEFUNC;
|
|
UNUSED(force);
|
|
|
|
if (m_sections.empty()) {
|
|
return;
|
|
}
|
|
|
|
bool isChanged = false;
|
|
|
|
DEFER {
|
|
if (isChanged) {
|
|
m_navigationChanged.notify();
|
|
}
|
|
};
|
|
|
|
INavigationSection* activeSec = findActive(m_sections);
|
|
if (activeSec && activeSec != sect) {
|
|
doDeactivateSection(activeSec);
|
|
}
|
|
|
|
if (!sect->active()) {
|
|
sect->setActive(true);
|
|
isChanged = true;
|
|
MYLOG() << "activated section: " << sect->name() << ", order: " << sect->index().order();
|
|
}
|
|
|
|
if (!panel) {
|
|
panel = firstEnabled(sect->panels());
|
|
}
|
|
|
|
INavigationPanel* activePanel = findActive(sect->panels());
|
|
if (activePanel && activePanel != panel) {
|
|
doDeactivatePanel(activePanel);
|
|
}
|
|
|
|
if (!panel) {
|
|
return;
|
|
}
|
|
|
|
if (!panel->active()) {
|
|
panel->setActive(true);
|
|
isChanged = true;
|
|
MYLOG() << "activated panel: " << panel->name() << ", order: " << panel->index().order();
|
|
}
|
|
|
|
if (!ctrl) {
|
|
ctrl = firstEnabled(panel->controls());
|
|
}
|
|
|
|
INavigationControl* activeCtrl = findActive(panel->controls());
|
|
if (activeCtrl && activeCtrl != ctrl) {
|
|
activeCtrl->setActive(false);
|
|
}
|
|
|
|
if (!ctrl) {
|
|
return;
|
|
}
|
|
|
|
if (!ctrl->active()) {
|
|
ctrl->setActive(true);
|
|
isChanged = true;
|
|
MYLOG() << "activated control: " << ctrl->name() << ", row: " << ctrl->index().row << ", column: " << ctrl->index().column;
|
|
}
|
|
}
|
|
|
|
inline QTextStream& operator<<(QTextStream& s, const std::string& v)
|
|
{
|
|
s << v.c_str();
|
|
return s;
|
|
}
|
|
|
|
void NavigationController::dump() const
|
|
{
|
|
QString str;
|
|
QTextStream stream(&str);
|
|
stream << "Navigation Dump:" << Qt::endl;
|
|
for (const INavigationSection* sect : m_sections) {
|
|
stream << "section: " << sect->name()
|
|
<< ", index: " << sect->index().to_string()
|
|
<< ", enabled: " << sect->enabled()
|
|
<< ", active: " << sect->active()
|
|
<< Qt::endl;
|
|
|
|
for (const INavigationPanel* panel : sect->panels()) {
|
|
stream << " panel: " << panel->name()
|
|
<< ", index: " << panel->index().to_string()
|
|
<< ", enabled: " << panel->enabled()
|
|
<< ", active: " << panel->active()
|
|
<< Qt::endl;
|
|
|
|
for (const INavigationControl* ctrl : panel->controls()) {
|
|
stream << " control: " << ctrl->name()
|
|
<< ", index: " << ctrl->index().to_string()
|
|
<< ", enabled: " << ctrl->enabled()
|
|
<< ", active: " << ctrl->active()
|
|
<< Qt::endl;
|
|
}
|
|
}
|
|
}
|
|
|
|
stream << Qt::endl;
|
|
|
|
std::cout << str.toStdString();
|
|
}
|