LCOV - code coverage report
Current view: top level - muse/framework/ui/internal - navigationcontroller.cpp Coverage Total Hit
Test: filtered_coverage.info Lines: 60.7 % 899 546
Test Date: 2026-11-05 04:07:02

            Line data    Source code
       1              : /*
       2              :  * SPDX-License-Identifier: GPL-3.0-only
       3              :  * MuseScore-CLA-applies
       4              :  *
       5              :  * MuseScore Studio
       6              :  * Music Composition & Notation
       7              :  *
       8              :  * Copyright (C) 2021 MuseScore Limited and others
       9              :  *
      10              :  * This program is free software: you can redistribute it and/or modify
      11              :  * it under the terms of the GNU General Public License version 3 as
      12              :  * published by the Free Software Foundation.
      13              :  *
      14              :  * This program is distributed in the hope that it will be useful,
      15              :  * but WITHOUT ANY WARRANTY; without even the implied warranty of
      16              :  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      17              :  * GNU General Public License for more details.
      18              :  *
      19              :  * You should have received a copy of the GNU General Public License
      20              :  * along with this program.  If not, see <https://www.gnu.org/licenses/>.
      21              :  */
      22              : #include "navigationcontroller.h"
      23              : 
      24              : #include <algorithm>
      25              : #include <limits>
      26              : #include <utility>
      27              : 
      28              : #include <QCoreApplication>
      29              : #include <QWindow>
      30              : #include <QTextStream>
      31              : 
      32              : #include "global/containers.h"
      33              : #include "global/defer.h"
      34              : 
      35              : #include "../navigationcommands.h"
      36              : 
      37              : #include "muse_framework_config.h"
      38              : #include "types/ret.h"
      39              : 
      40              : #ifdef MUSE_MODULE_DIAGNOSTICS
      41              : #include "diagnostics/diagnosticutils.h"
      42              : #endif
      43              : 
      44              : #include "log.h"
      45              : 
      46              : // #define NAVIGATION_LOGGING_ENABLED
      47              : 
      48              : #ifdef NAVIGATION_LOGGING_ENABLED
      49              : #define MYLOG() LOGI()
      50              : #else
      51              : #define MYLOG() LOGN()
      52              : #endif
      53              : 
      54              : using namespace muse;
      55              : using namespace muse::ui;
      56              : 
      57              : static const muse::UriQuery DEV_SHOW_CONTROLS_URI("muse://devtools/keynav/controls?modal=false");
      58              : 
      59              : using MoveDirection = NavigationController::MoveDirection;
      60              : using Event = INavigation::Event;
      61              : using ActivationType = INavigation::ActivationType;
      62              : 
      63              : // algorithms
      64              : template<class T>
      65          136 : static T* findNearestEnabled(const std::set<T*>& set, const INavigation::Index& currentIndex, MoveDirection direction)
      66              : {
      67          139 :     TRACEFUNC;
      68          136 :     T* ret = nullptr;
      69          476 :     for (T* v : set) {
      70          340 :         if (!v->enabled()) {
      71            3 :             continue;
      72              :         }
      73              : 
      74          337 :         switch (direction) {
      75          265 :         case MoveDirection::First: {
      76          265 :             if (!ret) {
      77          106 :                 ret = v;
      78          106 :                 continue;
      79              :             }
      80              : 
      81          159 :             if (v->index().row <= ret->index().row) {
      82          156 :                 if (v->index().column <= ret->index().column) {
      83           67 :                     ret = v;
      84           67 :                     continue;
      85              :                 }
      86              :             }
      87           92 :         } break;
      88           14 :         case MoveDirection::Last: {
      89           14 :             if (!ret) {
      90            6 :                 ret = v;
      91            6 :                 continue;
      92              :             }
      93              : 
      94            8 :             if (v->index().row >= ret->index().row) {
      95            4 :                 if (v->index().column >= ret->index().column) {
      96            1 :                     ret = v;
      97            1 :                     continue;
      98              :                 }
      99              :             }
     100            7 :         } break;
     101           39 :         case MoveDirection::Right: {
     102           39 :             if (v->index().row != currentIndex.row) {
     103            4 :                 continue;
     104              :             }
     105              : 
     106           35 :             if (v->index().column > currentIndex.column) {
     107            7 :                 if (!ret) {
     108            7 :                     ret = v;
     109            7 :                     continue;
     110              :                 }
     111              : 
     112            0 :                 if (v->index().column < ret->index().column) {
     113            0 :                     ret = v;
     114              :                 }
     115              :             }
     116           28 :         } break;
     117            7 :         case MoveDirection::Left: {
     118            7 :             if (v->index().row != currentIndex.row) {
     119            3 :                 continue;
     120              :             }
     121              : 
     122            4 :             if (v->index().column < currentIndex.column) {
     123            1 :                 if (!ret) {
     124            1 :                     ret = v;
     125            1 :                     continue;
     126              :                 }
     127              : 
     128            0 :                 if (v->index().column > ret->index().column) {
     129            0 :                     ret = v;
     130              :                 }
     131              :             }
     132            3 :         } break;
     133            6 :         case MoveDirection::Down: {
     134            6 :             if (v->index().column != currentIndex.column) {
     135            4 :                 continue;
     136              :             }
     137              : 
     138            2 :             if (v->index().row > currentIndex.row) {
     139            0 :                 if (!ret) {
     140            0 :                     ret = v;
     141            0 :                     continue;
     142              :                 }
     143              : 
     144            0 :                 if (v->index().row < ret->index().row) {
     145            0 :                     ret = v;
     146              :                 }
     147              :             }
     148            2 :         } break;
     149            6 :         case MoveDirection::Up: {
     150            6 :             if (v->index().column != currentIndex.column) {
     151            3 :                 continue;
     152              :             }
     153              : 
     154            3 :             if (v->index().row < currentIndex.row) {
     155            0 :                 if (!ret) {
     156            0 :                     ret = v;
     157            0 :                     continue;
     158              :                 }
     159              : 
     160            0 :                 if (v->index().row > ret->index().row) {
     161            0 :                     ret = v;
     162              :                 }
     163              :             }
     164            3 :         } break;
     165              :         }
     166              :     }
     167              : 
     168          136 :     return ret;
     169          136 : }
     170              : 
     171              : template<class T>
     172            0 : static T* firstEnabled(const std::set<T*>& set, const INavigation::Index& currentIndex, MoveDirection direction)
     173              : {
     174            0 :     if (set.empty()) {
     175            0 :         return nullptr;
     176              :     }
     177              : 
     178            0 :     IF_ASSERT_FAILED(direction == MoveDirection::Right || direction == MoveDirection::Down) {
     179            0 :         return nullptr;
     180              :     }
     181              : 
     182            0 :     return findNearestEnabled<T>(set, currentIndex, direction);
     183              : }
     184              : 
     185              : template<class T>
     186          107 : static T* firstEnabled(const std::set<T*>& set)
     187              : {
     188          107 :     if (set.empty()) {
     189            1 :         return nullptr;
     190              :     }
     191              : 
     192          106 :     return findNearestEnabled<T>(set, INavigation::Index(), MoveDirection::First);
     193              : }
     194              : 
     195              : template<class T>
     196            0 : static T* lastEnabled(const std::set<T*>& set, const INavigation::Index& currentIndex, MoveDirection direction)
     197              : {
     198            0 :     if (set.empty()) {
     199            0 :         return nullptr;
     200              :     }
     201              : 
     202            0 :     IF_ASSERT_FAILED(direction == MoveDirection::Left || (direction == MoveDirection::Up)) {
     203            0 :         return nullptr;
     204              :     }
     205              : 
     206            0 :     return findNearestEnabled<T>(set, currentIndex, direction);
     207              : }
     208              : 
     209              : template<class T>
     210            7 : static T* lastEnabled(const std::set<T*>& set)
     211              : {
     212            7 :     if (set.empty()) {
     213            1 :         return nullptr;
     214              :     }
     215              : 
     216            6 :     return findNearestEnabled<T>(set, INavigation::Index(), MoveDirection::Last);
     217              : }
     218              : 
     219              : template<class T>
     220           23 : static T* nextEnabled(const std::set<T*>& set, const INavigation::Index& currentIndex,
     221              :                       MoveDirection direction = MoveDirection::Right)
     222              : {
     223           23 :     if (set.empty()) {
     224            0 :         return nullptr;
     225              :     }
     226              : 
     227           23 :     return findNearestEnabled<T>(set, currentIndex, direction);
     228              : }
     229              : 
     230              : template<class T>
     231            1 : static T* prevEnabled(const std::set<T*>& set, const INavigation::Index& currentIndex, MoveDirection direction = MoveDirection::Left)
     232              : {
     233            1 :     if (set.empty()) {
     234            0 :         return nullptr;
     235              :     }
     236              : 
     237            1 :     return findNearestEnabled<T>(set, currentIndex, direction);
     238              : }
     239              : 
     240              : template<class T>
     241          221 : static T* findActive(const std::set<T*>& set)
     242              : {
     243          633 :     auto it = std::find_if(set.cbegin(), set.cend(), [](const T* s) {
     244          412 :         return s->active();
     245              :     });
     246              : 
     247          221 :     if (it != set.cend()) {
     248          125 :         return *it;
     249              :     }
     250              : 
     251           96 :     return nullptr;
     252              : }
     253              : 
     254              : template<class T>
     255            0 : static T* findByName(const std::set<T*>& set, const QString& name)
     256              : {
     257            0 :     auto it = std::find_if(set.cbegin(), set.cend(), [name](const T* s) {
     258            0 :         return s->name() == name;
     259              :     });
     260              : 
     261            0 :     if (it != set.cend()) {
     262            0 :         return *it;
     263              :     }
     264              : 
     265            0 :     return nullptr;
     266              : }
     267              : 
     268              : template<class T>
     269            0 : static T* findByIndex(const std::set<T*>& set, const INavigation::Index& idx)
     270              : {
     271            0 :     auto it = std::find_if(set.cbegin(), set.cend(), [idx](const T* s) {
     272            0 :         return s->index() == idx;
     273              :     });
     274              : 
     275            0 :     if (it != set.cend()) {
     276            0 :         return *it;
     277              :     }
     278              : 
     279            0 :     return nullptr;
     280              : }
     281              : 
     282           32 : void NavigationController::init()
     283              : {
     284           32 :     auto d = dispatcher();
     285           32 :     d->onRequest(this, ESCAPE_COMMAND, [this]() { onEscape(); return muse::make_ok(); });
     286              : 
     287           50 :     d->onRequest(this, NEXT_SECTION_COMMAND, [this]() { navigateTo(NavigationType::NextSection); return muse::make_ok(); });
     288           35 :     d->onRequest(this, PREV_SECTION_COMMAND, [this]() { navigateTo(NavigationType::PrevSection); return muse::make_ok(); });
     289           39 :     d->onRequest(this, NEXT_PANEL_COMMAND, [this]() { navigateTo(NavigationType::NextPanel); return muse::make_ok(); });
     290           35 :     d->onRequest(this, PREV_PANEL_COMMAND, [this]() { navigateTo(NavigationType::PrevPanel); return muse::make_ok(); });
     291           32 :     d->onRequest(this, NEXT_TAB_COMMAND, [this]() { navigateTo(NavigationType::NextPanel); return muse::make_ok(); });
     292           32 :     d->onRequest(this, PREV_TAB_COMMAND, [this]() { navigateTo(NavigationType::PrevPanel); return muse::make_ok(); });
     293              : 
     294           34 :     d->onRequest(this, RIGHT_COMMAND, [this]() { navigateTo(NavigationType::Right); return muse::make_ok(); });
     295           33 :     d->onRequest(this, LEFT_COMMAND, [this]() { navigateTo(NavigationType::Left); return muse::make_ok(); });
     296           33 :     d->onRequest(this, UP_COMMAND, [this]() { navigateTo(NavigationType::Up); return muse::make_ok(); });
     297           33 :     d->onRequest(this, DOWN_COMMAND, [this]() { navigateTo(NavigationType::Down); return muse::make_ok(); });
     298              : 
     299           32 :     d->onRequest(this, FIRST_CONTROL_COMMAND, [this]() { navigateTo(NavigationType::FirstControl); return muse::make_ok(); });
     300           32 :     d->onRequest(this, LAST_CONTROL_COMMAND, [this]() { navigateTo(NavigationType::LastControl); return muse::make_ok(); });
     301           32 :     d->onRequest(this, NEXTROW_CONTROL_COMMAND, [this]() { navigateTo(NavigationType::NextRowControl); return muse::make_ok(); });
     302           32 :     d->onRequest(this, PREVROW_CONTROL_COMMAND, [this]() { navigateTo(NavigationType::PrevRowControl); return muse::make_ok(); });
     303              : 
     304           32 :     d->onRequest(this, TRIGGER_CONTROL_COMMAND, [this]() { doTriggerControl(); return muse::make_ok(); });
     305              : 
     306              :     // compat
     307              :     {
     308              :         static const std::map<std::string, rcommand::Command> compatActionToCommand = {
     309              :             { "nav-next-section", NEXT_SECTION_COMMAND },
     310              :             { "nav-prev-section", PREV_SECTION_COMMAND },
     311              :             { "nav-next-panel", NEXT_PANEL_COMMAND },
     312              :             { "nav-prev-panel", PREV_PANEL_COMMAND },
     313              :             { "nav-next-tab", NEXT_PANEL_COMMAND },
     314              :             { "nav-prev-tab", PREV_PANEL_COMMAND },
     315              :             { "nav-trigger-control", TRIGGER_CONTROL_COMMAND },
     316              :             { "nav-right", RIGHT_COMMAND },
     317              :             { "nav-left", LEFT_COMMAND },
     318              :             { "nav-up", UP_COMMAND },
     319              :             { "nav-down", DOWN_COMMAND },
     320              :             { "nav-escape", ESCAPE_COMMAND },
     321              :             { "nav-first-control", FIRST_CONTROL_COMMAND },
     322              :             { "nav-last-control", LAST_CONTROL_COMMAND },
     323              :             { "nav-nextrow-control", NEXTROW_CONTROL_COMMAND },
     324              :             { "nav-prevrow-control", PREVROW_CONTROL_COMMAND },
     325           50 :         };
     326              : 
     327           32 :         auto ad = actionsDispatcher();
     328          544 :         for (const auto& [action, command] : compatActionToCommand) {
     329          512 :             ad->reg(this, action, [d, command]() { d->dispatch(command); });
     330              :         }
     331           32 :     }
     332              : 
     333           32 :     qApp->installEventFilter(this);
     334           33 : }
     335              : 
     336           64 : void NavigationController::reg(INavigationSection* section)
     337              : {
     338              :     //! TODO add check on valid state
     339           65 :     TRACEFUNC;
     340           64 :     m_sections.insert(section);
     341           64 :     section->setOnActiveRequested([this](INavigationSection* section, INavigationPanel* panel, INavigationControl* control,
     342              :                                          bool enableHighlight, ActivationType activationType) {
     343           30 :         if (control && activationType == ActivationType::ByMouse) {
     344            2 :             if (section->type() != INavigationSection::Type::Exclusive) {
     345            1 :                 return;
     346              :             }
     347              :         }
     348              : 
     349           29 :         if (enableHighlight) {
     350            0 :             setIsHighlight(true);
     351              :         }
     352              : 
     353           29 :         onActiveRequested(section, panel, control);
     354              :     });
     355           64 : }
     356              : 
     357           66 : void NavigationController::unreg(INavigationSection* section)
     358              : {
     359           67 :     TRACEFUNC;
     360           66 :     m_sections.erase(section);
     361           66 :     section->setOnActiveRequested(nullptr);
     362           66 :     section->enabledChanged().disconnect(this);
     363              : 
     364           66 :     if (m_prioritySection == section) {
     365            2 :         m_prioritySection = nullptr;
     366              :     }
     367           66 : }
     368              : 
     369            0 : const std::set<INavigationSection*>& NavigationController::sections() const
     370              : {
     371            0 :     return m_sections;
     372              : }
     373              : 
     374            6 : bool NavigationController::isHighlight() const
     375              : {
     376            6 :     return m_isHighlight;
     377              : }
     378              : 
     379           40 : void NavigationController::setIsHighlight(bool isHighlight)
     380              : {
     381           40 :     if (m_isHighlight == isHighlight) {
     382           14 :         return;
     383              :     }
     384              : 
     385           26 :     m_isHighlight = isHighlight;
     386           26 :     m_highlightChanged.notify();
     387              : }
     388              : 
     389            0 : muse::async::Notification NavigationController::highlightChanged() const
     390              : {
     391            0 :     return m_highlightChanged;
     392              : }
     393              : 
     394            0 : void NavigationController::setIsResetOnMousePress(bool arg)
     395              : {
     396            0 :     m_isResetOnMousePress = arg;
     397            0 : }
     398              : 
     399            3 : void NavigationController::resetIfNeed(QObject* watched)
     400              : {
     401            3 :     if (!m_isResetOnMousePress) {
     402            0 :         return;
     403              :     }
     404              : 
     405              : #ifdef MUSE_MODULE_DIAGNOSTICS
     406            3 :     if (muse::diagnostics::isDiagnosticHierarchy(watched)) {
     407            0 :         return;
     408              :     }
     409              : #endif
     410              : 
     411            3 :     auto activeCtrl = activeControl();
     412            3 :     if (activeCtrl && activeCtrl != m_defaultNavigationControl && watched == qApp) {
     413            3 :         resetNavigation();
     414              :     }
     415              : 
     416            3 :     setIsHighlight(false);
     417              : }
     418              : 
     419         2218 : bool NavigationController::eventFilter(QObject* watched, QEvent* event)
     420              : {
     421         2218 :     if (event->type() == QEvent::MouseButtonPress) {
     422            3 :         resetIfNeed(watched);
     423              :     }
     424              : 
     425         2218 :     return QObject::eventFilter(watched, event);
     426              : }
     427              : 
     428           36 : void NavigationController::navigateTo(NavigationController::NavigationType type)
     429              : {
     430           36 :     switch (type) {
     431           18 :     case NavigationType::NextSection:
     432           18 :         goToNextSection();
     433           18 :         break;
     434            3 :     case NavigationType::PrevSection:
     435            3 :         goToPrevSection(false);
     436            3 :         break;
     437            0 :     case NavigationType::PrevSectionActiveLastPanel:
     438            0 :         goToPrevSection(true);
     439            0 :         break;
     440            7 :     case NavigationType::NextPanel:
     441            7 :         goToNextPanel();
     442            7 :         break;
     443            3 :     case NavigationType::PrevPanel:
     444            3 :         goToPrevPanel();
     445            3 :         break;
     446            1 :     case NavigationType::Left:
     447            1 :         onLeft();
     448            1 :         break;
     449            2 :     case NavigationType::Right:
     450            2 :         onRight();
     451            2 :         break;
     452            1 :     case NavigationType::Up:
     453            1 :         onUp();
     454            1 :         break;
     455            1 :     case NavigationType::Down:
     456            1 :         onDown();
     457            1 :         break;
     458            0 :     case NavigationType::FirstControl:
     459            0 :         goToFirstControl();
     460            0 :         break;
     461            0 :     case NavigationType::LastControl:
     462            0 :         goToLastControl();
     463            0 :         break;
     464            0 :     case NavigationType::NextRowControl:
     465            0 :         goToNextRowControl();
     466            0 :         break;
     467            0 :     case NavigationType::PrevRowControl:
     468            0 :         goToPrevRowControl();
     469            0 :         break;
     470              :     }
     471              : 
     472           36 :     setIsHighlight(true);
     473           36 : }
     474              : 
     475            3 : void NavigationController::resetNavigation()
     476              : {
     477              :     MYLOG() << "===";
     478            3 :     INavigationSection* activeSec = this->activeSection();
     479            3 :     if (!activeSec) {
     480            0 :         return;
     481              :     }
     482              : 
     483            3 :     INavigationPanel* activePnl = findActive(activeSec->panels());
     484            3 :     if (activePnl) {
     485            3 :         INavigationControl* activeCtrl = findActive(activePnl->controls());
     486            3 :         if (activeCtrl) {
     487            3 :             activeCtrl->setActive(false);
     488              :         }
     489              : 
     490            3 :         activePnl->setActive(false);
     491              :     }
     492              : 
     493            3 :     activeSec->setActive(false);
     494              : 
     495            3 :     if (m_defaultNavigationControl) {
     496            2 :         INavigationPanel* panel = m_defaultNavigationControl->panel();
     497            2 :         INavigationSection* section = panel ? panel->section() : nullptr;
     498            2 :         if (section->enabled()) {
     499            1 :             m_defaultNavigationControl->setActive(true);
     500            1 :             m_navigationChanged.notify();
     501              :         }
     502              :     } else {
     503            1 :         doActivateFirst();
     504              :     }
     505              : }
     506              : 
     507           25 : void NavigationController::doActivateSection(INavigationSection* sect, bool isActivateLastPanel)
     508              : {
     509           26 :     TRACEFUNC;
     510           25 :     IF_ASSERT_FAILED(sect) {
     511            0 :         return;
     512              :     }
     513              : 
     514           75 :     for (INavigationPanel* panel : sect->panels()) {
     515           50 :         doDeactivatePanel(panel);
     516              :     }
     517              : 
     518           25 :     sect->setActive(true);
     519              :     MYLOG() << "activated section: " << sect->name() << ", order: " << sect->index().order();
     520              : 
     521           25 :     if (m_prioritySection == sect) {
     522            0 :         m_prioritySection = nullptr;
     523              :     }
     524              : 
     525           25 :     INavigationPanel* toActivatePanel = nullptr;
     526           25 :     if (isActivateLastPanel) {
     527            1 :         toActivatePanel = lastEnabled(sect->panels());
     528              :     } else {
     529           24 :         toActivatePanel = firstEnabled(sect->panels());
     530              :     }
     531              : 
     532           25 :     if (toActivatePanel) {
     533           25 :         doActivatePanel(toActivatePanel);
     534              :     }
     535           25 : }
     536              : 
     537           20 : void NavigationController::doDeactivateSection(INavigationSection* sect)
     538              : {
     539           21 :     TRACEFUNC;
     540           20 :     IF_ASSERT_FAILED(sect) {
     541            0 :         return;
     542              :     }
     543              : 
     544           20 :     saveLastActiveControl(sect);
     545              : 
     546           56 :     for (INavigationPanel* panel : sect->panels()) {
     547           36 :         doDeactivatePanel(panel);
     548              :     }
     549              : 
     550           20 :     sect->setActive(false);
     551           20 : }
     552              : 
     553           32 : void NavigationController::doActivatePanel(INavigationPanel* panel)
     554              : {
     555           33 :     TRACEFUNC;
     556           32 :     IF_ASSERT_FAILED(panel) {
     557            0 :         return;
     558              :     }
     559              : 
     560          128 :     for (INavigationControl* ctrl : panel->controls()) {
     561           96 :         doDeactivateControl(ctrl);
     562              :     }
     563              : 
     564           32 :     INavigation::EventPtr event = INavigation::Event::make(INavigation::Event::AboutActive);
     565           32 :     panel->onEvent(event);
     566              : 
     567           32 :     panel->setActive(true);
     568              :     MYLOG() << "activated panel: " << panel->name() << ", order: " << panel->index().order();
     569              : 
     570           32 :     INavigationControl* control = nullptr;
     571           32 :     QString ctrlName = event->data.value("controlName").toString();
     572           32 :     if (!ctrlName.isEmpty()) {
     573            0 :         control = findByName(panel->controls(), ctrlName);
     574            0 :         IF_ASSERT_FAILED(control) {
     575              :         }
     576              :     }
     577              : 
     578           32 :     if (!control) {
     579           32 :         QVariantList idxVal = event->data.value("controlIndex").toList();
     580           32 :         if (idxVal.count() == 2) {
     581            0 :             INavigation::Index ctrlIndex;
     582            0 :             ctrlIndex.row = idxVal.at(0).toInt();
     583            0 :             ctrlIndex.column = idxVal.at(1).toInt();
     584            0 :             control = findByIndex(panel->controls(), ctrlIndex);
     585            0 :             if (!control) {
     586            0 :                 bool isOptional = event->data.value("controlOptional").toBool();
     587            0 :                 if (!isOptional) {
     588            0 :                     IF_ASSERT_FAILED(control) {
     589              :                     }
     590              :                 }
     591              :             }
     592              :         }
     593           32 :     }
     594              : 
     595           32 :     if (!control) {
     596           32 :         control = firstEnabled(panel->controls());
     597              :     }
     598              : 
     599           32 :     if (control) {
     600           32 :         doActivateControl(control);
     601              :     }
     602           32 : }
     603              : 
     604           91 : void NavigationController::doDeactivatePanel(INavigationPanel* panel)
     605              : {
     606           92 :     TRACEFUNC;
     607           91 :     IF_ASSERT_FAILED(panel) {
     608            0 :         return;
     609              :     }
     610              : 
     611          364 :     for (INavigationControl* ctr : panel->controls()) {
     612          273 :         doDeactivateControl(ctr);
     613              :     }
     614              : 
     615           91 :     if (panel->active()) {
     616           22 :         panel->setActive(false);
     617              :     }
     618           91 : }
     619              : 
     620           37 : void NavigationController::doActivateControl(INavigationControl* ctrl)
     621              : {
     622           38 :     TRACEFUNC;
     623           37 :     IF_ASSERT_FAILED(ctrl) {
     624            0 :         return;
     625              :     }
     626              : 
     627           37 :     ctrl->setActive(true);
     628              :     MYLOG() << "activated control: " << ctrl->name() << ", row: " << ctrl->index().row << ", column: " << ctrl->index().column;
     629           37 : }
     630              : 
     631          374 : void NavigationController::doDeactivateControl(INavigationControl* ctrl)
     632              : {
     633          375 :     TRACEFUNC;
     634          374 :     IF_ASSERT_FAILED(ctrl) {
     635            0 :         return;
     636              :     }
     637              : 
     638          374 :     if (ctrl->active()) {
     639           31 :         ctrl->setActive(false);
     640              :     }
     641          374 : }
     642              : 
     643            4 : void NavigationController::doActivateFirst()
     644              : {
     645            4 :     if (m_sections.empty()) {
     646            0 :         return;
     647              :     }
     648              : 
     649            4 :     INavigationSection* first = firstEnabled(m_sections);
     650            4 :     if (first) {
     651            4 :         doActivateSection(first);
     652              :     }
     653              : }
     654              : 
     655            3 : void NavigationController::doActivateLast()
     656              : {
     657            3 :     if (m_sections.empty()) {
     658            0 :         return;
     659              :     }
     660              : 
     661            3 :     INavigationSection* last = lastEnabled(m_sections);
     662            3 :     if (last) {
     663            3 :         doActivateSection(last);
     664              :     }
     665              : }
     666              : 
     667           28 : INavigationSection* NavigationController::activeSection() const
     668              : {
     669           29 :     TRACEFUNC;
     670           28 :     if (m_sections.empty()) {
     671            0 :         return nullptr;
     672              :     }
     673           28 :     return findActive(m_sections);
     674           28 : }
     675              : 
     676           15 : INavigationPanel* NavigationController::activePanel() const
     677              : {
     678           16 :     TRACEFUNC;
     679           15 :     INavigationSection* activeSec = activeSection();
     680           15 :     if (!activeSec) {
     681            1 :         return nullptr;
     682              :     }
     683           14 :     return findActive(activeSec->panels());
     684           15 : }
     685              : 
     686           10 : INavigationControl* NavigationController::activeControl() const
     687              : {
     688           11 :     TRACEFUNC;
     689           10 :     INavigationPanel* activePanel = this->activePanel();
     690           10 :     if (!activePanel) {
     691            1 :         return nullptr;
     692              :     }
     693            9 :     return findActive(activePanel->controls());
     694           10 : }
     695              : 
     696            0 : const INavigationSection* NavigationController::findSection(const std::string& sectionName) const
     697              : {
     698            0 :     const INavigationSection* sec = findByName(m_sections, QString::fromStdString(sectionName));
     699            0 :     return sec;
     700              : }
     701              : 
     702            0 : const INavigationPanel* NavigationController::findPanel(const std::string& sectionName, const std::string& panelName) const
     703              : {
     704            0 :     const INavigationSection* sec = findByName(m_sections, QString::fromStdString(sectionName));
     705            0 :     const INavigationPanel* pnl = sec ? findByName(sec->panels(), QString::fromStdString(panelName)) : nullptr;
     706            0 :     return pnl;
     707              : }
     708              : 
     709            0 : const INavigationControl* NavigationController::findControl(const std::string& sectionName, const std::string& panelName,
     710              :                                                             const std::string& controlName) const
     711              : {
     712            0 :     const INavigationSection* sec = findByName(m_sections, QString::fromStdString(sectionName));
     713            0 :     const INavigationPanel* pnl = sec ? findByName(sec->panels(), QString::fromStdString(panelName)) : nullptr;
     714            0 :     const INavigationControl* ctrl = pnl ? findByName(pnl->controls(), QString::fromStdString(controlName)) : nullptr;
     715              : 
     716            0 :     return ctrl;
     717              : }
     718              : 
     719            3 : void NavigationController::setDefaultNavigationControl(INavigationControl* control)
     720              : {
     721            3 :     m_defaultNavigationControl = control;
     722            3 : }
     723              : 
     724           12 : void NavigationController::setPrioritySection(INavigationSection* section)
     725              : {
     726           12 :     if (!section) {
     727            0 :         m_prioritySection = nullptr;
     728            0 :         return;
     729              :     }
     730              : 
     731           12 :     if (m_sections.find(section) == m_sections.end()) {
     732            0 :         LOGW() << "unable to set priority section, section is not registered, name: " << section->name();
     733            0 :         return;
     734              :     }
     735              : 
     736           12 :     m_prioritySection = section;
     737              : 
     738           12 :     section->enabledChanged().onReceive(this, [this, section](bool enabled) {
     739            6 :         if (!enabled && section->active()) {
     740            3 :             restoreLastActiveControl(section);
     741              :         }
     742            6 :     }, async::Asyncable::Mode::SetReplace);
     743              : }
     744              : 
     745           20 : INavigationSection* NavigationController::takePrioritySection(const INavigationSection* activeSec)
     746              : {
     747           20 :     INavigationSection* prioritySec = m_prioritySection;
     748           20 :     m_prioritySection = nullptr;
     749              : 
     750           20 :     if (!prioritySec || prioritySec == activeSec || !prioritySec->enabled()) {
     751           13 :         return nullptr;
     752              :     }
     753              : 
     754            7 :     return prioritySec;
     755              : }
     756              : 
     757           24 : void NavigationController::saveLastActiveControl(INavigationSection* sect)
     758              : {
     759           24 :     if (!sect->enabled() || sect == m_prioritySection) {
     760            5 :         return;
     761              :     }
     762              : 
     763           19 :     INavigationPanel* activePanel = findActive(sect->panels());
     764           19 :     INavigationControl* ctrl = activePanel ? findActive(activePanel->controls()) : nullptr;
     765           19 :     if (!ctrl) {
     766            1 :         return;
     767              :     }
     768              : 
     769           18 :     m_lastActiveControl = ctrl;
     770              : }
     771              : 
     772            3 : INavigationControl* NavigationController::takeLastActiveControl()
     773              : {
     774            3 :     INavigationControl* ctrl = std::exchange(m_lastActiveControl, nullptr);
     775              : 
     776            3 :     const bool isAvailable = ctrl && std::any_of(m_sections.cbegin(), m_sections.cend(), [ctrl](const INavigationSection* sect) {
     777            5 :         if (!sect->enabled()) {
     778            2 :             return false;
     779              :         }
     780              : 
     781            3 :         const std::set<INavigationPanel*>& panels = sect->panels();
     782            3 :         return std::any_of(panels.cbegin(), panels.cend(), [ctrl](const INavigationPanel* panel) {
     783            5 :             return panel->enabled() && muse::contains(panel->controls(), ctrl);
     784            3 :         });
     785            3 :     });
     786              : 
     787            3 :     return isAvailable && ctrl->enabled() ? ctrl : m_defaultNavigationControl;
     788              : }
     789              : 
     790            3 : void NavigationController::restoreLastActiveControl(INavigationSection* sect)
     791              : {
     792            3 :     INavigationControl* ctrl = takeLastActiveControl();
     793              : 
     794            3 :     doDeactivateSection(sect);
     795              : 
     796            3 :     if (ctrl) {
     797            3 :         ctrl->requestActive();
     798              :     }
     799              : 
     800            3 :     m_navigationChanged.notify();
     801            3 : }
     802              : 
     803            0 : muse::async::Notification NavigationController::navigationChanged() const
     804              : {
     805            0 :     return m_navigationChanged;
     806              : }
     807              : 
     808           20 : void NavigationController::goToNextSection()
     809              : {
     810           21 :     TRACEFUNC;
     811              :     MYLOG() << "====";
     812           20 :     if (m_sections.empty()) {
     813            0 :         return;
     814              :     }
     815              : 
     816           20 :     INavigationSection* activeSec = findActive(m_sections);
     817           20 :     if (activeSec && activeSec->type() == INavigationSection::Type::Exclusive) {
     818            3 :         INavigationPanel* first = firstEnabled(activeSec->panels());
     819            3 :         if (first) {
     820            3 :             doActivatePanel(first);
     821            3 :             m_navigationChanged.notify();
     822              :         }
     823            3 :         return;
     824              :     }
     825              : 
     826           17 :     if (INavigationSection* prioritySec = takePrioritySection(activeSec)) {
     827            6 :         if (activeSec) {
     828            4 :             doDeactivateSection(activeSec);
     829              :         }
     830            6 :         doActivateSection(prioritySec);
     831            6 :         m_navigationChanged.notify();
     832            6 :         return;
     833              :     }
     834              : 
     835           11 :     if (!activeSec) { // no any active
     836            1 :         doActivateFirst();
     837            1 :         return;
     838              :     }
     839              : 
     840           10 :     doDeactivateSection(activeSec);
     841              : 
     842           10 :     INavigationSection* nextSec = nextEnabled(m_sections, activeSec->index());
     843           10 :     if (!nextSec) { // active is last
     844            6 :         nextSec = firstEnabled(m_sections); // the first to be the next
     845              :     }
     846           10 :     if (!nextSec) {
     847            0 :         LOGI() << "no enabled sections!";
     848            0 :         return;
     849              :     }
     850              : 
     851           10 :     LOGI() << "nextSec: " << nextSec->name() << ", enabled: " << nextSec->enabled();
     852              : 
     853           10 :     doActivateSection(nextSec);
     854              : 
     855           10 :     m_navigationChanged.notify();
     856           20 : }
     857              : 
     858            4 : void NavigationController::goToPrevSection(bool isActivateLastPanel)
     859              : {
     860            5 :     TRACEFUNC;
     861              :     MYLOG() << "====";
     862            4 :     if (m_sections.empty()) {
     863            0 :         return;
     864              :     }
     865              : 
     866            4 :     INavigationSection* activeSec = findActive(m_sections);
     867            4 :     if (activeSec && activeSec->type() == INavigationSection::Type::Exclusive) {
     868            1 :         INavigationPanel* first = firstEnabled(activeSec->panels());
     869            1 :         if (first) {
     870            1 :             doActivatePanel(first);
     871            1 :             m_navigationChanged.notify();
     872              :         }
     873            1 :         return;
     874              :     }
     875              : 
     876            3 :     if (INavigationSection* prioritySec = takePrioritySection(activeSec)) {
     877            1 :         if (activeSec) {
     878            1 :             doDeactivateSection(activeSec);
     879              :         }
     880            1 :         doActivateSection(prioritySec);
     881            1 :         m_navigationChanged.notify();
     882            1 :         return;
     883              :     }
     884              : 
     885            2 :     if (!activeSec) { // no any active
     886            1 :         doActivateLast();
     887            1 :         return;
     888              :     }
     889              : 
     890            1 :     doDeactivateSection(activeSec);
     891              : 
     892            1 :     INavigationSection* prevSec = prevEnabled(m_sections, activeSec->index());
     893            1 :     if (!prevSec) { // active is first
     894            0 :         prevSec = lastEnabled(m_sections); // the last to be the prev
     895              :     }
     896              : 
     897            1 :     doActivateSection(prevSec, isActivateLastPanel);
     898              : 
     899            1 :     m_navigationChanged.notify();
     900            4 : }
     901              : 
     902            7 : void NavigationController::goToNextPanel()
     903              : {
     904            8 :     TRACEFUNC;
     905              :     MYLOG() << "====";
     906            7 :     INavigationSection* activeSec = activeSection();
     907            7 :     if (!activeSec) {
     908            2 :         doActivateFirst();
     909            2 :         m_navigationChanged.notify();
     910            2 :         return;
     911              :     }
     912              : 
     913            5 :     INavigationPanel* activePanel = findActive(activeSec->panels());
     914            5 :     if (!activePanel) { // no any active
     915            1 :         INavigationPanel* first = firstEnabled(activeSec->panels());
     916            1 :         if (first) {
     917            0 :             doActivatePanel(first);
     918            0 :             m_navigationChanged.notify();
     919            0 :             return;
     920              :         }
     921              : 
     922              :         MYLOG() << "the active section " << activeSec->name() << " has no enabled panels, go to the next section";
     923            1 :         goToNextSection();
     924            1 :         return;
     925              :     }
     926              : 
     927            4 :     saveLastActiveControl(activeSec);
     928            4 :     doDeactivatePanel(activePanel);
     929              : 
     930            4 :     INavigationPanel* nextPanel = nextEnabled(activeSec->panels(), activePanel->index());
     931            4 :     if (nextPanel) {
     932            2 :         doActivatePanel(nextPanel);
     933            2 :         m_navigationChanged.notify();
     934            2 :         return;
     935              :     }
     936              : 
     937            2 :     if (activeSec->type() == INavigationSection::Type::Exclusive) {
     938            1 :         INavigationPanel* first = firstEnabled(activeSec->panels());
     939            1 :         if (first) {
     940            1 :             doActivatePanel(first);
     941            1 :             m_navigationChanged.notify();
     942              :         }
     943            1 :         return;
     944              :     }
     945              : 
     946              :     // active is last panel, go to first panel in next section
     947            1 :     goToNextSection();
     948            7 : }
     949              : 
     950            3 : void NavigationController::goToPrevPanel()
     951              : {
     952            4 :     TRACEFUNC;
     953              :     MYLOG() << "====";
     954            3 :     INavigationSection* activeSec = activeSection();
     955            3 :     if (!activeSec) {
     956            2 :         doActivateLast();
     957            2 :         m_navigationChanged.notify();
     958            2 :         return;
     959              :     }
     960              : 
     961            1 :     INavigationPanel* activePanel = findActive(activeSec->panels());
     962            1 :     if (!activePanel) { // no any active
     963            1 :         INavigationPanel* lastPanel = lastEnabled(activeSec->panels());
     964            1 :         if (lastPanel) {
     965            0 :             doActivatePanel(lastPanel);
     966            0 :             m_navigationChanged.notify();
     967            0 :             return;
     968              :         }
     969              : 
     970              :         MYLOG() << "the active section " << activeSec->name() << " has no enabled panels, go to the prev section";
     971            1 :         goToPrevSection(true);
     972            1 :         return;
     973              :     }
     974              : 
     975            0 :     saveLastActiveControl(activeSec);
     976            0 :     doDeactivatePanel(activePanel);
     977              : 
     978            0 :     INavigationPanel* prevPanel = prevEnabled(activeSec->panels(), activePanel->index());
     979            0 :     if (prevPanel) {
     980            0 :         doActivatePanel(prevPanel);
     981            0 :         m_navigationChanged.notify();
     982            0 :         return;
     983              :     }
     984              : 
     985            0 :     if (activeSec->type() == INavigationSection::Type::Exclusive) {
     986            0 :         INavigationPanel* lastPanel = lastEnabled(activeSec->panels());
     987            0 :         if (lastPanel) {
     988            0 :             doActivatePanel(lastPanel);
     989            0 :             m_navigationChanged.notify();
     990              :         }
     991            0 :         return;
     992              :     }
     993              : 
     994              :     // active is first, go to last panel in prev section
     995            0 :     goToPrevSection(true);
     996            3 : }
     997              : 
     998            2 : void NavigationController::onRight()
     999              : {
    1000            3 :     TRACEFUNC;
    1001            2 :     INavigationPanel* activePanel = this->activePanel();
    1002            2 :     if (!activePanel) {
    1003            0 :         return;
    1004              :     }
    1005              : 
    1006            2 :     INavigation::EventPtr e = Event::make(Event::Right);
    1007            2 :     activePanel->onEvent(e);
    1008            2 :     if (e->accepted) {
    1009            0 :         return;
    1010              :     }
    1011              : 
    1012            2 :     INavigationControl* activeCtrl = findActive(activePanel->controls());
    1013            2 :     if (activeCtrl) {
    1014            2 :         activeCtrl->onEvent(e);
    1015            2 :         if (e->accepted) {
    1016            0 :             return;
    1017              :         }
    1018              :     }
    1019              : 
    1020            2 :     goToControl(MoveDirection::Right, activePanel);
    1021            2 : }
    1022              : 
    1023            1 : void NavigationController::onLeft()
    1024              : {
    1025            2 :     TRACEFUNC;
    1026            1 :     INavigationPanel* activePanel = this->activePanel();
    1027            1 :     if (!activePanel) {
    1028            0 :         return;
    1029              :     }
    1030              : 
    1031            1 :     INavigation::EventPtr e = Event::make(Event::Left);
    1032            1 :     activePanel->onEvent(e);
    1033            1 :     if (e->accepted) {
    1034            0 :         return;
    1035              :     }
    1036              : 
    1037            1 :     INavigationControl* activeCtrl = findActive(activePanel->controls());
    1038            1 :     if (activeCtrl) {
    1039            1 :         activeCtrl->onEvent(e);
    1040            1 :         if (e->accepted) {
    1041            0 :             return;
    1042              :         }
    1043              :     }
    1044              : 
    1045            1 :     goToControl(MoveDirection::Left, activePanel);
    1046            1 : }
    1047              : 
    1048            1 : void NavigationController::onDown()
    1049              : {
    1050            2 :     TRACEFUNC;
    1051            1 :     INavigationPanel* activePanel = this->activePanel();
    1052            1 :     if (!activePanel) {
    1053            0 :         return;
    1054              :     }
    1055              : 
    1056            1 :     INavigation::EventPtr e = Event::make(Event::Down);
    1057            1 :     activePanel->onEvent(e);
    1058            1 :     if (e->accepted) {
    1059            0 :         return;
    1060              :     }
    1061              : 
    1062            1 :     INavigationControl* activeCtrl = findActive(activePanel->controls());
    1063            1 :     if (activeCtrl) {
    1064            1 :         activeCtrl->onEvent(e);
    1065            1 :         if (e->accepted) {
    1066            0 :             return;
    1067              :         }
    1068              :     }
    1069              : 
    1070            1 :     goToControl(MoveDirection::Down, activePanel);
    1071            1 : }
    1072              : 
    1073            1 : void NavigationController::onUp()
    1074              : {
    1075            2 :     TRACEFUNC;
    1076            1 :     INavigationPanel* activePanel = this->activePanel();
    1077            1 :     if (!activePanel) {
    1078            0 :         return;
    1079              :     }
    1080              : 
    1081            1 :     INavigation::EventPtr e = Event::make(Event::Up);
    1082            1 :     activePanel->onEvent(e);
    1083            1 :     if (e->accepted) {
    1084            0 :         return;
    1085              :     }
    1086              : 
    1087            1 :     INavigationControl* activeCtrl = findActive(activePanel->controls());
    1088            1 :     if (activeCtrl) {
    1089            1 :         activeCtrl->onEvent(e);
    1090            1 :         if (e->accepted) {
    1091            0 :             return;
    1092              :         }
    1093              :     }
    1094              : 
    1095            1 :     goToControl(MoveDirection::Up, activePanel);
    1096            1 : }
    1097              : 
    1098            0 : void NavigationController::onEscape()
    1099              : {
    1100            0 :     TRACEFUNC;
    1101              :     MYLOG() << "====";
    1102            0 :     INavigationSection* activeSec = activeSection();
    1103            0 :     if (!activeSec) {
    1104            0 :         return;
    1105              :     }
    1106              : 
    1107            0 :     INavigation::EventPtr e = Event::make(Event::Escape);
    1108            0 :     activeSec->onEvent(e);
    1109            0 :     if (e->accepted) {
    1110            0 :         return;
    1111              :     }
    1112              : 
    1113            0 :     INavigationPanel* activePanel = findActive(activeSec->panels());
    1114            0 :     if (!activePanel) {
    1115            0 :         return;
    1116              :     }
    1117              : 
    1118            0 :     activePanel->onEvent(e);
    1119            0 :     if (e->accepted) {
    1120            0 :         return;
    1121              :     }
    1122              : 
    1123            0 :     INavigationControl* activeCtrl = findActive(activePanel->controls());
    1124            0 :     if (!activeCtrl) {
    1125            0 :         return;
    1126              :     }
    1127              : 
    1128            0 :     activeCtrl->onEvent(e);
    1129            0 :     if (e->accepted) {
    1130            0 :         return;
    1131              :     }
    1132              : 
    1133            0 :     activeCtrl->setActive(false);
    1134              : 
    1135            0 :     if (m_defaultNavigationControl) {
    1136            0 :         INavigationPanel* defaultPanel = m_defaultNavigationControl->panel();
    1137            0 :         if (!defaultPanel) {
    1138            0 :             return;
    1139              :         }
    1140              : 
    1141            0 :         INavigationSection* defaultSection = defaultPanel->section();
    1142            0 :         if (!defaultSection) {
    1143            0 :             return;
    1144              :         }
    1145              : 
    1146            0 :         onActiveRequested(defaultSection, defaultPanel, m_defaultNavigationControl, true);
    1147              :     } else {
    1148            0 :         m_navigationChanged.notify();
    1149              :     }
    1150            0 : }
    1151              : 
    1152            0 : void NavigationController::goToFirstControl()
    1153              : {
    1154              :     MYLOG() << "====";
    1155            0 :     goToControl(MoveDirection::First);
    1156            0 : }
    1157              : 
    1158            0 : void NavigationController::goToLastControl()
    1159              : {
    1160              :     MYLOG() << "====";
    1161            0 :     goToControl(MoveDirection::Last);
    1162            0 : }
    1163              : 
    1164            0 : void NavigationController::goToNextRowControl()
    1165              : {
    1166            0 :     TRACEFUNC;
    1167              :     MYLOG() << "====";
    1168            0 :     INavigationPanel* activePanel = this->activePanel();
    1169            0 :     if (!activePanel) {
    1170            0 :         return;
    1171              :     }
    1172              : 
    1173            0 :     INavigationControl* activeControl = findActive(activePanel->controls());
    1174            0 :     if (activeControl) {
    1175            0 :         doDeactivateControl(activeControl);
    1176              :     }
    1177              : 
    1178            0 :     INavigationControl* toControl = nullptr;
    1179            0 :     if (activeControl) {
    1180            0 :         INavigation::Index index = activeControl->index();
    1181            0 :         index.column = 0;
    1182            0 :         toControl = nextEnabled(activePanel->controls(), index, MoveDirection::Down);
    1183            0 :         if (!toControl) { // last row
    1184            0 :             toControl = firstEnabled(activePanel->controls());
    1185              :         }
    1186              :     } else {
    1187            0 :         toControl = firstEnabled(activePanel->controls());
    1188              :     }
    1189              : 
    1190            0 :     if (toControl) {
    1191            0 :         doActivateControl(toControl);
    1192              :     }
    1193              : 
    1194            0 :     m_navigationChanged.notify();
    1195            0 : }
    1196              : 
    1197            0 : void NavigationController::goToPrevRowControl()
    1198              : {
    1199            0 :     TRACEFUNC;
    1200              :     MYLOG() << "====";
    1201            0 :     INavigationPanel* activePanel = this->activePanel();
    1202            0 :     if (!activePanel) {
    1203            0 :         return;
    1204              :     }
    1205              : 
    1206            0 :     INavigationControl* activeControl = findActive(activePanel->controls());
    1207            0 :     if (activeControl) {
    1208            0 :         doDeactivateControl(activeControl);
    1209              :     }
    1210              : 
    1211            0 :     INavigationControl* toControl = nullptr;
    1212            0 :     if (activeControl) {
    1213            0 :         INavigation::Index index = activeControl->index();
    1214            0 :         index.column = 0;
    1215            0 :         toControl = prevEnabled(activePanel->controls(), index, MoveDirection::Up);
    1216            0 :         if (!toControl) { // first row
    1217            0 :             toControl = lastEnabled(activePanel->controls());
    1218              :         }
    1219              :     } else {
    1220            0 :         toControl = lastEnabled(activePanel->controls());
    1221              :     }
    1222              : 
    1223            0 :     if (toControl) {
    1224            0 :         doActivateControl(toControl);
    1225              :     }
    1226              : 
    1227            0 :     m_navigationChanged.notify();
    1228            0 : }
    1229              : 
    1230            5 : void NavigationController::goToControl(MoveDirection direction, INavigationPanel* activePanel)
    1231              : {
    1232            6 :     TRACEFUNC;
    1233              :     MYLOG() << "direction: " << direction;
    1234            5 :     if (!activePanel) {
    1235            0 :         activePanel = this->activePanel();
    1236              :     }
    1237              : 
    1238            5 :     if (!activePanel) {
    1239            0 :         return;
    1240              :     }
    1241              : 
    1242            5 :     INavigationControl* activeControl = findActive(activePanel->controls());
    1243            5 :     INavigationControl* toControl = nullptr;
    1244              : 
    1245            5 :     bool tryOtherOrientation = activePanel->direction() == INavigationPanel::Direction::Horizontal
    1246            5 :                                || activePanel->direction() == INavigationPanel::Direction::Vertical;
    1247              : 
    1248            5 :     switch (direction) {
    1249            0 :     case MoveDirection::First: {
    1250            0 :         toControl = firstEnabled(activePanel->controls());
    1251            0 :     } break;
    1252            0 :     case MoveDirection::Last: {
    1253            0 :         toControl = lastEnabled(activePanel->controls());
    1254            0 :     } break;
    1255            2 :     case MoveDirection::Right: {
    1256            2 :         if (!activeControl) { // no any active
    1257            0 :             toControl = firstEnabled(activePanel->controls(), INavigation::Index(), MoveDirection::Right);
    1258              :         } else {
    1259            2 :             toControl = nextEnabled(activePanel->controls(), activeControl->index(), MoveDirection::Right);
    1260            2 :             if (!toControl && tryOtherOrientation) {
    1261            1 :                 toControl = nextEnabled(activePanel->controls(), activeControl->index(), MoveDirection::Down);
    1262              :             }
    1263            2 :             if (!toControl) { // active is last
    1264            1 :                 if (activePanel->direction() == INavigationPanel::Direction::Horizontal) {
    1265            1 :                     toControl = firstEnabled(activePanel->controls());
    1266            1 :                     break;
    1267              :                 }
    1268              : 
    1269            0 :                 INavigation::Index index = activeControl->index();
    1270            0 :                 index.column = -1;
    1271            0 :                 toControl = firstEnabled(activePanel->controls(), index, MoveDirection::Right); // the first to be the next
    1272              : 
    1273            0 :                 if (!toControl && tryOtherOrientation) {
    1274            0 :                     index = activeControl->index();
    1275            0 :                     index.row = -1;
    1276            0 :                     toControl = nextEnabled(activePanel->controls(), index, MoveDirection::Down);
    1277              :                 }
    1278              :             }
    1279              :         }
    1280            1 :     } break;
    1281            1 :     case MoveDirection::Down: {
    1282            1 :         if (!activeControl) { // no any active
    1283            0 :             toControl = firstEnabled(activePanel->controls(), INavigation::Index(), MoveDirection::Down);
    1284              :         } else {
    1285            1 :             toControl = nextEnabled(activePanel->controls(), activeControl->index(), MoveDirection::Down);
    1286            1 :             if (!toControl && tryOtherOrientation) {
    1287            1 :                 toControl = nextEnabled(activePanel->controls(), activeControl->index(), MoveDirection::Right);
    1288              :             }
    1289            1 :             if (!toControl) { // active is last
    1290            1 :                 if (activePanel->direction() == INavigationPanel::Direction::Vertical) {
    1291            1 :                     toControl = firstEnabled(activePanel->controls());
    1292            1 :                     break;
    1293              :                 }
    1294              : 
    1295            0 :                 INavigation::Index index = activeControl->index();
    1296            0 :                 index.row = -1;
    1297            0 :                 toControl = firstEnabled(activePanel->controls(), index, MoveDirection::Down); // the first to be the next
    1298              : 
    1299            0 :                 if (!toControl && tryOtherOrientation) {
    1300            0 :                     index = activeControl->index();
    1301            0 :                     index.column = -1;
    1302            0 :                     toControl = nextEnabled(activePanel->controls(), index, MoveDirection::Right);
    1303              :                 }
    1304              :             }
    1305              :         }
    1306            0 :     } break;
    1307            1 :     case MoveDirection::Left: {
    1308            1 :         if (!activeControl) { // no any active
    1309            0 :             toControl = lastEnabled(activePanel->controls(), INavigation::Index(), MoveDirection::Left);
    1310              :         } else {
    1311            1 :             toControl = nextEnabled(activePanel->controls(), activeControl->index(), MoveDirection::Left);
    1312            1 :             if (!toControl && tryOtherOrientation) {
    1313            1 :                 toControl = nextEnabled(activePanel->controls(), activeControl->index(), MoveDirection::Up);
    1314              :             }
    1315            1 :             if (!toControl) { // active is first
    1316            1 :                 if (activePanel->direction() == INavigationPanel::Direction::Horizontal) {
    1317            1 :                     toControl = lastEnabled(activePanel->controls());
    1318            1 :                     break;
    1319              :                 }
    1320              : 
    1321            0 :                 INavigation::Index index = activeControl->index();
    1322            0 :                 index.column = std::numeric_limits<int>::max();
    1323            0 :                 toControl = lastEnabled(activePanel->controls(), index, MoveDirection::Left); // the last to be the next
    1324              : 
    1325            0 :                 if (!toControl && tryOtherOrientation) {
    1326            0 :                     index = activeControl->index();
    1327            0 :                     index.row = std::numeric_limits<int>::max();
    1328            0 :                     toControl = nextEnabled(activePanel->controls(), index, MoveDirection::Up);
    1329              :                 }
    1330              :             }
    1331              :         }
    1332            0 :     } break;
    1333            1 :     case MoveDirection::Up: {
    1334            1 :         if (!activeControl) { // no any active
    1335            0 :             toControl = lastEnabled(activePanel->controls(), INavigation::Index(), MoveDirection::Up);
    1336              :         } else {
    1337            1 :             toControl = nextEnabled(activePanel->controls(), activeControl->index(), MoveDirection::Up);
    1338            1 :             if (!toControl && tryOtherOrientation) {
    1339            1 :                 toControl = nextEnabled(activePanel->controls(), activeControl->index(), MoveDirection::Left);
    1340              :             }
    1341            1 :             if (!toControl) { // active is first
    1342            1 :                 if (activePanel->direction() == INavigationPanel::Direction::Vertical) {
    1343            1 :                     toControl = lastEnabled(activePanel->controls());
    1344            1 :                     break;
    1345              :                 }
    1346              : 
    1347            0 :                 INavigation::Index index = activeControl->index();
    1348            0 :                 index.row = std::numeric_limits<int>::max();
    1349            0 :                 toControl = lastEnabled(activePanel->controls(), index, MoveDirection::Up); // the last to be the next
    1350              : 
    1351            0 :                 if (!toControl && tryOtherOrientation) {
    1352            0 :                     index = activeControl->index();
    1353            0 :                     index.column = std::numeric_limits<int>::max();
    1354            0 :                     toControl = nextEnabled(activePanel->controls(), index, MoveDirection::Left);
    1355              :                 }
    1356              :             }
    1357              :         }
    1358            0 :     } break;
    1359              :     }
    1360              : 
    1361            5 :     if (!toControl) {
    1362            0 :         return;
    1363              :     }
    1364              : 
    1365              :     //! NOTE Maybe just one control (or just one enabled control)
    1366            5 :     if (toControl == activeControl) {
    1367            0 :         return;
    1368              :     }
    1369              : 
    1370            5 :     if (activeControl) {
    1371              :         MYLOG() << "current activated control: " << activeControl->name()
    1372              :                 << ", row: " << activeControl->index().row
    1373              :                 << ", column: " << activeControl->index().column;
    1374              : 
    1375            5 :         doDeactivateControl(activeControl);
    1376              :     }
    1377              : 
    1378            5 :     doActivateControl(toControl);
    1379              : 
    1380            5 :     m_navigationChanged.notify();
    1381            5 : }
    1382              : 
    1383            0 : void NavigationController::doTriggerControl()
    1384              : {
    1385            0 :     TRACEFUNC;
    1386              :     MYLOG() << "====";
    1387            0 :     INavigationPanel* activePanel = this->activePanel();
    1388            0 :     if (!activePanel) {
    1389            0 :         return;
    1390              :     }
    1391              : 
    1392            0 :     INavigation::EventPtr e = Event::make(Event::Trigger);
    1393            0 :     activePanel->onEvent(e);
    1394            0 :     if (e->accepted) {
    1395            0 :         return;
    1396              :     }
    1397              : 
    1398            0 :     INavigationControl* activeCtrl = findActive(activePanel->controls());
    1399            0 :     if (!activeCtrl) {
    1400            0 :         return;
    1401              :     }
    1402              : 
    1403              :     MYLOG() << "triggered control: " << activeCtrl->name();
    1404            0 :     activeCtrl->onEvent(e);
    1405            0 :     if (e->accepted) {
    1406            0 :         return;
    1407              :     }
    1408              : 
    1409            0 :     activeCtrl->trigger();
    1410            0 : }
    1411              : 
    1412            0 : bool NavigationController::requestActivateByName(const std::string& sectName, const std::string& panelName, const std::string& controlName)
    1413              : {
    1414            0 :     INavigationSection* section = findByName(m_sections, QString::fromStdString(sectName));
    1415            0 :     if (!section) {
    1416            0 :         LOGE() << "not found section with name: " << sectName;
    1417            0 :         return false;
    1418              :     }
    1419              : 
    1420            0 :     INavigationPanel* panel = findByName(section->panels(), QString::fromStdString(panelName));
    1421            0 :     if (!panel) {
    1422            0 :         LOGE() << "not found panel with name: " << panelName << ", section: " << sectName;
    1423            0 :         return false;
    1424              :     }
    1425              : 
    1426            0 :     INavigationControl* control = findByName(panel->controls(), QString::fromStdString(controlName));
    1427            0 :     if (!control) {
    1428            0 :         LOGE() << "not found control with name: " << controlName << ", panel: " << panelName << ", section: " << sectName;
    1429            0 :         QString has = "has:\n";
    1430            0 :         for (const INavigationControl* c : panel->controls()) {
    1431            0 :             has += c->name() + "\n";
    1432              :         }
    1433            0 :         LOGI() << has;
    1434            0 :         return false;
    1435            0 :     }
    1436              : 
    1437            0 :     onActiveRequested(section, panel, control, true);
    1438            0 :     return true;
    1439              : }
    1440              : 
    1441            0 : bool NavigationController::requestActivateByIndex(const std::string& sectName, const std::string& panelName,
    1442              :                                                   const INavigation::Index& controlIndex)
    1443              : {
    1444            0 :     INavigationSection* section = findByName(m_sections, QString::fromStdString(sectName));
    1445            0 :     if (!section) {
    1446            0 :         LOGE() << "not found section with name: " << sectName;
    1447            0 :         return false;
    1448              :     }
    1449              : 
    1450            0 :     INavigationPanel* panel = findByName(section->panels(), QString::fromStdString(panelName));
    1451            0 :     if (!panel) {
    1452            0 :         LOGE() << "not found panel with name: " << panelName << ", section: " << sectName;
    1453            0 :         return false;
    1454              :     }
    1455              : 
    1456            0 :     INavigationControl* control = findByIndex(panel->controls(), controlIndex);
    1457            0 :     if (!control) {
    1458            0 :         LOGE() << "not found control with index: " << controlIndex.to_string() << ", panel: " << panelName << ", section: " << sectName;
    1459            0 :         std::string has = "has:\n";
    1460            0 :         for (const INavigationControl* c : panel->controls()) {
    1461            0 :             has += c->index().to_string() + "\n";
    1462              :         }
    1463            0 :         LOGI() << has;
    1464            0 :         return false;
    1465            0 :     }
    1466              : 
    1467            0 :     onActiveRequested(section, panel, control, true);
    1468            0 :     return true;
    1469              : }
    1470              : 
    1471           29 : void NavigationController::onActiveRequested(INavigationSection* sect, INavigationPanel* panel, INavigationControl* ctrl, bool force)
    1472              : {
    1473           30 :     TRACEFUNC;
    1474              :     UNUSED(force);
    1475              : 
    1476           29 :     if (m_sections.empty()) {
    1477            0 :         return;
    1478              :     }
    1479              : 
    1480           29 :     bool isChanged = false;
    1481              : 
    1482           29 :     DEFER {
    1483           29 :         if (isChanged) {
    1484           29 :             m_navigationChanged.notify();
    1485              :         }
    1486           29 :     };
    1487              : 
    1488           29 :     INavigationSection* activeSec = findActive(m_sections);
    1489           29 :     if (activeSec && activeSec != sect) {
    1490            1 :         doDeactivateSection(activeSec);
    1491              :     }
    1492              : 
    1493           29 :     if (!sect->active()) {
    1494           28 :         sect->setActive(true);
    1495           28 :         isChanged = true;
    1496              :         MYLOG() << "activated section: " << sect->name() << ", order: " << sect->index().order();
    1497              :     }
    1498              : 
    1499           29 :     if (m_prioritySection == sect) {
    1500            1 :         m_prioritySection = nullptr;
    1501              :     }
    1502              : 
    1503           29 :     if (!panel) {
    1504           16 :         panel = firstEnabled(sect->panels());
    1505              :     }
    1506              : 
    1507           29 :     INavigationPanel* activePanel = findActive(sect->panels());
    1508           29 :     if (activePanel && activePanel != panel) {
    1509            1 :         doDeactivatePanel(activePanel);
    1510              :     }
    1511              : 
    1512           29 :     if (!panel) {
    1513            0 :         return;
    1514              :     }
    1515              : 
    1516           29 :     if (!panel->active()) {
    1517           29 :         panel->setActive(true);
    1518           29 :         isChanged = true;
    1519              :         MYLOG() << "activated panel: " << panel->name() << ", order: " << panel->index().order();
    1520              :     }
    1521              : 
    1522           29 :     if (!ctrl) {
    1523           17 :         ctrl = firstEnabled(panel->controls());
    1524              :     }
    1525              : 
    1526           29 :     INavigationControl* activeCtrl = findActive(panel->controls());
    1527           29 :     if (activeCtrl && activeCtrl != ctrl) {
    1528            0 :         activeCtrl->setActive(false);
    1529              :     }
    1530              : 
    1531           29 :     if (!ctrl) {
    1532            0 :         return;
    1533              :     }
    1534              : 
    1535           29 :     if (!ctrl->active()) {
    1536           29 :         ctrl->setActive(true);
    1537           29 :         isChanged = true;
    1538              :         MYLOG() << "activated control: " << ctrl->name() << ", row: " << ctrl->index().row << ", column: " << ctrl->index().column;
    1539              :     }
    1540           29 : }
    1541              : 
    1542            0 : inline QTextStream& operator<<(QTextStream& s, const std::string& v)
    1543              : {
    1544            0 :     s << v.c_str();
    1545            0 :     return s;
    1546              : }
    1547              : 
    1548            0 : void NavigationController::dump() const
    1549              : {
    1550            0 :     QString str;
    1551            0 :     QTextStream stream(&str);
    1552            0 :     stream << "Navigation Dump:" << Qt::endl;
    1553            0 :     for (const INavigationSection* sect : m_sections) {
    1554            0 :         stream << "section: " << sect->name()
    1555            0 :                << ", index: " << sect->index().to_string()
    1556            0 :                << ", enabled: " << sect->enabled()
    1557            0 :                << ", active: " << sect->active()
    1558            0 :                << Qt::endl;
    1559              : 
    1560            0 :         for (const INavigationPanel* panel : sect->panels()) {
    1561            0 :             stream << "    panel: " << panel->name()
    1562            0 :                    << ", index: " << panel->index().to_string()
    1563            0 :                    << ", enabled: " << panel->enabled()
    1564            0 :                    << ", active: " << panel->active()
    1565            0 :                    << Qt::endl;
    1566              : 
    1567            0 :             for (const INavigationControl* ctrl : panel->controls()) {
    1568            0 :                 stream << "        control: " << ctrl->name()
    1569            0 :                        << ", index: " << ctrl->index().to_string()
    1570            0 :                        << ", enabled: " << ctrl->enabled()
    1571            0 :                        << ", active: " << ctrl->active()
    1572            0 :                        << Qt::endl;
    1573              :             }
    1574              :         }
    1575              :     }
    1576              : 
    1577            0 :     stream << Qt::endl;
    1578              : 
    1579            0 :     std::cout << str.toStdString();
    1580            0 : }
        

Generated by: LCOV version 2.0-1