diff --git a/bin/app/data/color_schemes/bad_rainbow.xml b/bin/app/data/color_schemes/bad_rainbow.xml index efa60252..c741ac5e 100644 --- a/bin/app/data/color_schemes/bad_rainbow.xml +++ b/bin/app/data/color_schemes/bad_rainbow.xml @@ -4,6 +4,7 @@ #494949 #CCCCCC + #9BF51D transparent #80999999 @@ -106,7 +107,6 @@ #AAAAAA - #999999 <text>black</text> @@ -130,7 +130,6 @@ <title> <text>#A0A0A0</text> <background>transparent</background> - <hover>#2F2F2F</hover> @@ -255,7 +254,7 @@ #20FFFFFF - #FFFFFF + #FFFFFF #FFFFFF @@ -292,11 +291,11 @@ #FFCC3B - #FFD05A + #FFD05A transparent - #FFFFFF + #FFFFFF #FFE8A6 @@ -309,11 +308,11 @@ #2FC2D6 - #38D3E8 + #38D3E8 transparent - #FFFFFF + #FFFFFF #96DAE3 @@ -335,7 +334,7 @@ #80FFFFFF - #FFFFFF + #FFFFFF #FFFFFF @@ -357,11 +356,11 @@ #4E82F6 - #6793F6 + #6793F6 #20FFFFFF - #FFFFFF + #FFFFFF #FFFFFF @@ -382,8 +381,14 @@ - #CCCCCC + #494949 + + #FFFFFF + + + #FFFFFF + @@ -391,7 +396,7 @@ #80FFFFFF - #FFFFFF + #FFFFFF #FFFFFF @@ -451,7 +456,7 @@ #4E82F6 - #6793F6 + #6793F6 @@ -463,7 +468,7 @@ #873E3E - #944444 + #944444 @@ -477,55 +482,24 @@ - - #797979 - + #797979 + #797979 + #797979 - - default - + #00A0B6 + #DEA400 + #FFFFFF + #BA5ECC - - default - + #D93D5F + #D93D5F - - #BA5ECC - + #4E82F6 + #4E82F6 + #4E82F6 + #4E82F6 - - #DEA400 - #FFFFFF - - - - #00A0B6 - - - - #D93D5F - - - type_argument - - - - #4E82F6 - - - include - - - include - - - include - - - - #666666 - #6D6D6D - + #666666 diff --git a/bin/app/data/color_schemes/bright.xml b/bin/app/data/color_schemes/bright.xml index c90db38d..95adc1ad 100644 --- a/bin/app/data/color_schemes/bright.xml +++ b/bin/app/data/color_schemes/bright.xml @@ -4,6 +4,7 @@ white #A2A2A2 + #FF5900 transparent #80999999 @@ -106,7 +107,6 @@ #D8D8D8 - #E0E0E0 <text>black</text> @@ -130,7 +130,6 @@ <title> <text>#909090</text> <background>transparent</background> - <hover>#F5F5F5</hover> @@ -253,7 +252,7 @@ #203C3C3C - #3C3C3C + #3C3C3C @@ -284,11 +283,11 @@ #F4D07D - #F4BC3D + #F4BC3D transparent - #3C3C3C + #3C3C3C #FFEDC4 @@ -301,11 +300,11 @@ #81C1E3 - #72B3D6 + #72B3D6 transparent - #3C3C3C + #3C3C3C #BBDDED @@ -327,7 +326,7 @@ #3D3D3D - black + black #3D3D3D @@ -346,11 +345,11 @@ #BEE0AF - #A6D492 + #A6D492 #203C3C3C - #3C3C3C + #3C3C3C #D7F7C8 @@ -374,7 +373,7 @@ #803C3C3C - #3C3C3C + #3C3C3C @@ -425,7 +424,7 @@ #BEE0AF - #A6D492 + #A6D492 @@ -437,7 +436,7 @@ #EFC5C5 - #EFB3B3 + #EFB3B3 @@ -451,55 +450,24 @@ - - #878787 - + #878787 + #878787 + #878787 - - default - + #4B9FC4 + #F4BC3D + #F56B3D + #A37ACC - - default - + #CF6B7C + #CF6B7C - - #A37ACC - + #719660 + #719660 + #719660 + #719660 - - #F4BC3D - #F56B3D - - - - #4B9FC4 - - - - #CF6B7C - - - type_argument - - - - #719660 - - - include - - - include - - - include - - - - #CCC - #BBB - + #CCC diff --git a/bin/app/data/color_schemes/dark.xml b/bin/app/data/color_schemes/dark.xml index 2c093a94..bc77ef45 100644 --- a/bin/app/data/color_schemes/dark.xml +++ b/bin/app/data/color_schemes/dark.xml @@ -4,6 +4,7 @@ #272728 #CCC + #FFFF4D transparent #80999999 @@ -106,7 +107,6 @@ #5C5C5C - #6C6C6C <text>#F7F7F7</text> @@ -130,7 +130,6 @@ <title> <text>#A0A0A0</text> <background>transparent</background> - <hover>#2F2F2F</hover> @@ -256,7 +255,7 @@ #20C3C3C3 - #C3C3C3 + #C3C3C3 @@ -287,11 +286,11 @@ #7A681F - #9C8528 + #9C8528 transparent - #C3C3C3 + #C3C3C3 #544815 @@ -304,11 +303,11 @@ #21516B - #2A6484 + #2A6484 transparent - #C3C3C3 + #C3C3C3 #17384A @@ -330,7 +329,7 @@ #80C3C3C3 - #C3C3C3 + #C3C3C3 #C3C3C3 @@ -349,11 +348,11 @@ #1B564E - #21695F + #21695F #20C3C3C3 - #C3C3C3 + #C3C3C3 #123B35 @@ -377,7 +376,7 @@ #80C3C3C3 - #C3C3C3 + #C3C3C3 @@ -428,7 +427,7 @@ #1B564E - #1E6158 + #1E6158 @@ -440,7 +439,7 @@ #78282D - #872D33 + #872D33 @@ -454,55 +453,24 @@ - - #797979 - + #797979 + #797979 + #797979 - - default - + #2D6A8C + #9C8528 + #F7F7F7 + #8A5A94 - - default - + #C14057 + #C14057 - - #8A5A94 - + #247368 + #247368 + #247368 + #247368 - - #9C8528 - #F7F7F7 - - - - #2D6A8C - - - - #C14057 - - - type_argument - - - - #247368 - - - include - - - include - - - include - - - - #4D4D4D - #6D6D6D - + #4D4D4D diff --git a/bin/app/data/gui/code_view/code_view.css b/bin/app/data/gui/code_view/code_view.css index dfea9af5..203ca93f 100644 --- a/bin/app/data/gui/code_view/code_view.css +++ b/bin/app/data/gui/code_view/code_view.css @@ -24,6 +24,14 @@ border: none; } +#focus_indicator { + background: ; +} + +#focus_indicator[focused=true] { + background: ; +} + #code_navigation QLabel { color: ; font-size: px; @@ -131,14 +139,14 @@ border: none; } -#title_bar:hover { - background-color: ; -} - #title_bar[hovering=false] { border-top: 1px solid ; } +#title_bar[focused=true] { + border-left: 4px solid ; +} + #title_bar_single { background-color: ; padding-right: 5px; @@ -203,21 +211,32 @@ padding-bottom: 2px; } -#code_file #code_snippet #scope_name:hover { - background-color: ; +#code_file #code_snippet #scope_name[focused=true] { + color: ; } #code_file #code_snippet #dots { background-color: ; - padding-right: 16px; + border-right: 3px solid transparent; + margin-right: 5px; + padding-right: 8px; text-align: right; } +#code_file #code_snippet #dots[focused=true] { + border-right: 3px solid ; +} + #code_file #code_snippet #scope_name { padding-left: 4px; text-align: left; } +#code_file #code_snippet #scope_name[focused=true] { + padding-left: 4px; + text-align: left; +} + #line_number_area { background-color: ; border: none; diff --git a/bin/app/data/gui/graph_view/graph_view.css b/bin/app/data/gui/graph_view/graph_view.css index 0ecfd046..7662bc83 100644 --- a/bin/app/data/gui/graph_view/graph_view.css +++ b/bin/app/data/gui/graph_view/graph_view.css @@ -22,6 +22,10 @@ QPushButton:pressed { background: ; } +#focus_indicator { + background: ; +} + #expand_button { max-width: 26px; max-height: 26px; diff --git a/src/lib/CMakeLists.txt b/src/lib/CMakeLists.txt index e08d2b40..1be99114 100644 --- a/src/lib/CMakeLists.txt +++ b/src/lib/CMakeLists.txt @@ -474,6 +474,12 @@ add_files( utility/messaging/type/error/MessageErrorsHelpMessage.h utility/messaging/type/error/MessageShowError.h + utility/messaging/type/focus/MessageFocusChanged.h + utility/messaging/type/focus/MessageFocusedSearchView.h + utility/messaging/type/focus/MessageFocusIn.h + utility/messaging/type/focus/MessageFocusOut.h + utility/messaging/type/focus/MessageFocusView.h + utility/messaging/type/graph/MessageActivateEdge.h utility/messaging/type/graph/MessageActivateNodes.h utility/messaging/type/graph/MessageActivateTrailEdge.h @@ -513,8 +519,6 @@ add_files( utility/messaging/type/MessageClearStatusView.h utility/messaging/type/MessageCloseProject.h utility/messaging/type/MessageFlushUpdates.h - utility/messaging/type/MessageFocusIn.h - utility/messaging/type/MessageFocusOut.h utility/messaging/type/MessageLoadProject.h utility/messaging/type/MessageLogFilterChanged.h utility/messaging/type/MessageProjectEdit.h @@ -531,6 +535,7 @@ add_files( utility/messaging/type/MessageSwitchColorScheme.h utility/messaging/type/MessageTooltipHide.h utility/messaging/type/MessageTooltipShow.h + utility/messaging/type/MessageWindowChanged.h utility/messaging/type/MessageWindowClosed.h utility/messaging/type/MessageWindowFocus.h utility/messaging/type/MessageZoom.h diff --git a/src/lib/component/ComponentManager.cpp b/src/lib/component/ComponentManager.cpp index a4728753..35cded7b 100644 --- a/src/lib/component/ComponentManager.cpp +++ b/src/lib/component/ComponentManager.cpp @@ -41,7 +41,7 @@ void ComponentManager::setupMain(ViewLayout* viewLayout, Id appId) { std::shared_ptr compositeView = m_componentFactory.getViewFactory()->createCompositeView( - viewLayout, CompositeView::DIRECTION_HORIZONTAL, "Search"); + viewLayout, CompositeView::DIRECTION_HORIZONTAL, "Search", 0); m_singleViews.push_back(compositeView); std::shared_ptr undoRedoView = @@ -134,7 +134,7 @@ void ComponentManager::setupTab(ViewLayout* viewLayout, Id tabId, ScreenSearchSe { std::shared_ptr compositeView = m_componentFactory.getViewFactory()->createCompositeView( - viewLayout, CompositeView::DIRECTION_HORIZONTAL, "Search"); + viewLayout, CompositeView::DIRECTION_HORIZONTAL, "Search", tabId); m_singleViews.push_back(compositeView); std::shared_ptr undoRedoComponent = m_componentFactory.createUndoRedoComponent( @@ -209,6 +209,29 @@ void ComponentManager::refreshViews() } } +View* ComponentManager::getView(const std::string& name) const +{ + for (const std::shared_ptr& component: m_components) + { + View* view = component->getViewPtr(); + + if (view && view->getName() == name) + { + return view; + } + } + + for (const std::shared_ptr& view: m_singleViews) + { + if (view->getName() == name) + { + return view.get(); + } + } + + return nullptr; +} + std::shared_ptr ComponentManager::getDialogView(DialogView::UseCase useCase) const { auto it = m_dialogViews.find(useCase); diff --git a/src/lib/component/ComponentManager.h b/src/lib/component/ComponentManager.h index 4af5f1b7..6c8eff33 100644 --- a/src/lib/component/ComponentManager.h +++ b/src/lib/component/ComponentManager.h @@ -31,6 +31,7 @@ public: void clearComponents(); void refreshViews(); + View* getView(const std::string& name) const; std::shared_ptr getDialogView(DialogView::UseCase useCase) const; private: diff --git a/src/lib/component/Tab.cpp b/src/lib/component/Tab.cpp index d75ea73a..6238ebe0 100644 --- a/src/lib/component/Tab.cpp +++ b/src/lib/component/Tab.cpp @@ -1,5 +1,8 @@ #include "Tab.h" +#include "CodeView.h" +#include "GraphView.h" + Tab::Tab( Id tabId, const ViewFactory* viewFactory, @@ -19,6 +22,11 @@ Tab::~Tab() m_componentManager.teardownTab(m_screenSearchSender); } +Id Tab::getSchedulerId() const +{ + return m_tabId; +} + void Tab::setParentLayout(ViewLayout* parentLayout) { m_parentLayout = parentLayout; @@ -72,6 +80,34 @@ void Tab::setViewEnabled(View* view, bool enabled) } } +void Tab::handleMessage(MessageFocusView* message) +{ + GraphView* graphView = dynamic_cast(m_componentManager.getView(GraphView::VIEW_NAME)); + CodeView* codeView = dynamic_cast(m_componentManager.getView(CodeView::VIEW_NAME)); + + if (!graphView || !codeView) + { + LOG_ERROR("Tab has no code or graph view."); + return; + } + + MessageFocusView::ViewType type = message->type; + if (type == MessageFocusView::ViewType::TOGGLE) + { + if (graphView->hasNavigationFocus()) + { + type = MessageFocusView::ViewType::CODE; + } + else + { + type = MessageFocusView::ViewType::GRAPH; + } + } + + graphView->setNavigationFocus(type == MessageFocusView::ViewType::GRAPH); + codeView->setNavigationFocus(type == MessageFocusView::ViewType::CODE); +} + void Tab::handleMessage(MessageRefreshUI* message) { m_componentManager.refreshViews(); diff --git a/src/lib/component/Tab.h b/src/lib/component/Tab.h index 3597c105..17825be5 100644 --- a/src/lib/component/Tab.h +++ b/src/lib/component/Tab.h @@ -4,6 +4,7 @@ #include #include "ComponentManager.h" +#include "MessageFocusView.h" #include "MessageListener.h" #include "MessageRefreshUI.h" #include "View.h" @@ -11,6 +12,7 @@ class Tab : public ViewLayout + , public MessageListener , public MessageListener { public: @@ -20,6 +22,8 @@ public: ScreenSearchSender* screenSearchSender); virtual ~Tab(); + Id getSchedulerId() const override; + void setParentLayout(ViewLayout* parentLayout); // ViewLayout implementation @@ -32,6 +36,7 @@ public: void setViewEnabled(View* view, bool enabled) override; private: + void handleMessage(MessageFocusView* message) override; void handleMessage(MessageRefreshUI* message) override; const Id m_tabId; diff --git a/src/lib/component/controller/CodeController.cpp b/src/lib/component/controller/CodeController.cpp index cfe5ef35..fe86a79a 100644 --- a/src/lib/component/controller/CodeController.cpp +++ b/src/lib/component/controller/CodeController.cpp @@ -1,10 +1,12 @@ #include "CodeController.h" +#include #include #include "Application.h" #include "ApplicationSettings.h" #include "FileInfo.h" +#include "MessageFocusView.h" #include "MessageMoveIDECursor.h" #include "MessageShowError.h" #include "MessageStatus.h" @@ -314,6 +316,8 @@ void CodeController::handleMessage(MessageCodeReference* message) { iterateReference(next); } + + MessageFocusView(MessageFocusView::ViewType::CODE).dispatch(); } void CodeController::handleMessage(MessageCodeShowDefinition* message) @@ -427,6 +431,14 @@ void CodeController::handleMessage(MessageErrorCountClear* message) } } +void CodeController::handleMessage(MessageFocusChanged* message) +{ + if (message->isReplayed() && message->isFromCode()) + { + m_codeParams.locationIdToFocus = message->tokenOrLocationId; + } +} + void CodeController::handleMessage(MessageFlushUpdates* message) { showFiles(m_codeParams, m_scrollParams, true); @@ -434,12 +446,12 @@ void CodeController::handleMessage(MessageFlushUpdates* message) void CodeController::handleMessage(MessageFocusIn* message) { - getView()->focusTokenIds(message->tokenIds); + getView()->coFocusTokenIds(message->tokenIds); } void CodeController::handleMessage(MessageFocusOut* message) { - getView()->defocusTokenIds(); + getView()->deCoFocusTokenIds(); } void CodeController::handleMessage(MessageScrollToLine* message) @@ -522,6 +534,99 @@ void CodeController::handleMessage(MessageShowScope* message) showFiles(m_codeParams, CodeScrollParams(), !message->isReplayed()); } +void CodeController::handleMessage(MessageToNextCodeReference* message) +{ + FilePath currentFilePath = message->filePath; + size_t currentLineNumber = message->lineNumber; + bool next = message->next; + bool inListMode = getView()->isInListMode(); + + if (currentFilePath.empty()) + { + return; + } + + std::pair referencePos = findClosestReferenceIndex( + m_references, currentFilePath, currentLineNumber, next); + std::pair localReferencePos = findClosestReferenceIndex( + m_localReferences, currentFilePath, currentLineNumber, next); + + int referenceIndex = referencePos.first; + int referenceFileIndex = referencePos.second; + int localReferenceIndex = localReferencePos.first; + int localReferenceFileIndex = localReferencePos.second; + + if (referenceIndex >= 0 && localReferenceIndex >= 0) + { + if (referenceFileIndex == localReferenceFileIndex) + { + if (referenceFileIndex < 0) + { + if (m_references[referenceIndex].filePath != + m_localReferences[localReferenceIndex].filePath || + m_references[referenceIndex].lineNumber > + m_localReferences[localReferenceIndex].lineNumber) + { + localReferenceIndex = -1; + } + else + { + referenceIndex = -1; + } + } + else if (referenceFileIndex == 0) + { + if (std::abs( + static_cast(m_references[referenceIndex].lineNumber) - + static_cast(currentLineNumber)) < + std::abs( + static_cast(m_localReferences[localReferenceIndex].lineNumber) - + static_cast(currentLineNumber))) + { + localReferenceIndex = -1; + } + else + { + referenceIndex = -1; + } + } + else + { + if (m_references[referenceIndex].filePath != + m_localReferences[localReferenceIndex].filePath || + m_references[referenceIndex].lineNumber < + m_localReferences[localReferenceIndex].lineNumber) + { + localReferenceIndex = -1; + } + else + { + referenceIndex = -1; + } + } + } + else if (referenceFileIndex == 0) + { + localReferenceIndex = -1; + } + else if (localReferenceFileIndex == 0) + { + referenceIndex = -1; + } + } + + if (localReferenceIndex >= 0) + { + m_localReferenceIndex = localReferenceIndex; + showCurrentLocalReference(true); + } + else if (referenceIndex >= 0) + { + m_referenceIndex = referenceIndex; + showCurrentReference(); + } +} + CodeView* CodeController::getView() const { return Controller::getView(); @@ -899,6 +1004,7 @@ void CodeController::createReferences() ref.tokenId = 0; ref.locationId = location->getLocationId(); ref.locationType = location->getType(); + ref.lineNumber = location->getLineNumber(); m_references.push_back(ref); return; } @@ -910,6 +1016,7 @@ void CodeController::createReferences() ref.tokenId = i; ref.locationId = location->getLocationId(); ref.locationType = location->getType(); + ref.lineNumber = location->getLineNumber(); std::map::const_iterator it = scopeLocationIds.find(i); if (it != scopeLocationIds.end()) @@ -947,6 +1054,7 @@ void CodeController::createLocalReferences(const std::set& localSymbolIds) ref.filePath = location->getFilePath(); ref.locationId = location->getLocationId(); ref.locationType = location->getType(); + ref.lineNumber = location->getLineNumber(); m_localReferences.push_back(ref); return; } @@ -999,8 +1107,7 @@ void CodeController::iterateReference(bool next) } } - const Reference& ref = m_references[m_referenceIndex - 1]; - MessageShowReference(m_referenceIndex, ref.tokenId, ref.locationId, true).dispatch(); + showCurrentReference(); } void CodeController::iterateLocalReference(bool next, bool updateView) @@ -1031,6 +1138,17 @@ void CodeController::iterateLocalReference(bool next, bool updateView) } } + showCurrentLocalReference(updateView); +} + +void CodeController::showCurrentReference() +{ + const Reference& ref = m_references[m_referenceIndex]; + MessageShowReference(m_referenceIndex, ref.tokenId, ref.locationId, true).dispatch(); +} + +void CodeController::showCurrentLocalReference(bool updateView) +{ const Reference& ref = m_localReferences[m_localReferenceIndex]; m_codeParams.currentActiveLocalLocationIds = {ref.locationId}; @@ -1049,6 +1167,57 @@ void CodeController::iterateLocalReference(bool next, bool updateView) showFiles(m_codeParams, toReferenceScrollParams(ref), updateView); } +std::pair CodeController::findClosestReferenceIndex( + const std::vector& references, + const FilePath& currentFilePath, + size_t currentLineNumber, + bool next) const +{ + int referenceIndex = -1; + bool beforeCurrentFile = true; + + for (size_t i = 0; i < references.size(); i++) + { + if (references[i].filePath == currentFilePath) + { + if (!next) + { + if (references[i].lineNumber < currentLineNumber) + { + referenceIndex = static_cast(i); + } + else + { + return {referenceIndex, beforeCurrentFile ? -1 : 0}; + } + } + else if (references[i].lineNumber > currentLineNumber) + { + return {static_cast(i), 0}; + } + + beforeCurrentFile = false; + } + else if (!next) + { + if (beforeCurrentFile) + { + referenceIndex = static_cast(i); + } + else + { + return {referenceIndex, 1}; + } + } + else if (next && !beforeCurrentFile) + { + return {static_cast(i), 1}; + } + } + + return {referenceIndex, beforeCurrentFile ? -1 : 1}; +} + void CodeController::expandVisibleFiles(bool useSingleFileCache) { TRACE(); @@ -1300,9 +1469,7 @@ CodeScrollParams CodeController::firstReferenceScrollParams() const const Reference& ref = m_references.front(); return CodeScrollParams::toReference( - ref.filePath, - ref.scopeLocationId ? ref.scopeLocationId : ref.locationId, - CodeScrollParams::Target::TOP); + ref.filePath, ref.locationId, ref.scopeLocationId, CodeScrollParams::Target::TOP); } return CodeScrollParams(); @@ -1318,7 +1485,7 @@ CodeScrollParams CodeController::definitionReferenceScrollParams(const std::vect if (ref.scopeLocationId && ref.tokenId == activeTokenId) { return CodeScrollParams::toReference( - ref.filePath, ref.scopeLocationId, CodeScrollParams::Target::TOP); + ref.filePath, ref.locationId, ref.scopeLocationId, CodeScrollParams::Target::TOP); } } @@ -1327,7 +1494,7 @@ CodeScrollParams CodeController::definitionReferenceScrollParams(const std::vect if (ref.tokenId == activeTokenId) { return CodeScrollParams::toReference( - ref.filePath, ref.locationId, CodeScrollParams::Target::TOP); + ref.filePath, ref.locationId, ref.scopeLocationId, CodeScrollParams::Target::TOP); } } } @@ -1338,9 +1505,7 @@ CodeScrollParams CodeController::definitionReferenceScrollParams(const std::vect CodeScrollParams CodeController::toReferenceScrollParams(const Reference& ref) const { return CodeScrollParams::toReference( - ref.filePath, - ref.scopeLocationId ? ref.scopeLocationId : ref.locationId, - CodeScrollParams::Target::CENTER); + ref.filePath, ref.locationId, ref.scopeLocationId, CodeScrollParams::Target::CENTER); } void CodeController::saveOrRestoreViewMode(MessageBase* message) diff --git a/src/lib/component/controller/CodeController.h b/src/lib/component/controller/CodeController.h index 16e5fb10..29c62154 100644 --- a/src/lib/component/controller/CodeController.h +++ b/src/lib/component/controller/CodeController.h @@ -19,6 +19,7 @@ #include "MessageCodeShowDefinition.h" #include "MessageDeactivateEdge.h" #include "MessageErrorCountClear.h" +#include "MessageFocusChanged.h" #include "MessageFlushUpdates.h" #include "MessageFocusIn.h" #include "MessageFocusOut.h" @@ -28,6 +29,7 @@ #include "MessageShowError.h" #include "MessageShowReference.h" #include "MessageShowScope.h" +#include "MessageToNextCodeReference.h" #include "types.h" #include "CodeView.h" @@ -54,6 +56,7 @@ class CodeController , public MessageListener , public MessageListener , public MessageListener + , public MessageListener , public MessageListener , public MessageListener , public MessageListener @@ -62,6 +65,7 @@ class CodeController , public MessageListener , public MessageListener , public MessageListener + , public MessageListener { public: CodeController(StorageAccess* storageAccess); @@ -77,6 +81,7 @@ private: Id locationId = 0; Id scopeLocationId = 0; LocationType locationType = LOCATION_TOKEN; + size_t lineNumber = 0; }; void handleMessage(MessageActivateErrors* message) override; @@ -92,6 +97,7 @@ private: void handleMessage(MessageCodeShowDefinition* message) override; void handleMessage(MessageDeactivateEdge* message) override; void handleMessage(MessageErrorCountClear* message) override; + void handleMessage(MessageFocusChanged* message) override; void handleMessage(MessageFlushUpdates* message) override; void handleMessage(MessageFocusIn* message) override; void handleMessage(MessageFocusOut* message) override; @@ -100,6 +106,7 @@ private: void handleMessage(MessageShowError* message) override; void handleMessage(MessageShowReference* message) override; void handleMessage(MessageShowScope* message) override; + void handleMessage(MessageToNextCodeReference* message) override; CodeView* getView() const; @@ -133,6 +140,15 @@ private: void iterateReference(bool next); void iterateLocalReference(bool next, bool updateView); + void showCurrentReference(); + void showCurrentLocalReference(bool updateView); + + std::pair findClosestReferenceIndex( + const std::vector& references, + const FilePath& currentFilePath, + size_t currentLineNumber, + bool next) const; + void expandVisibleFiles(bool useSingleFileCache); CodeFileParams* addSourceLocations(std::shared_ptr locationFile); void setFileState( diff --git a/src/lib/component/controller/GraphController.cpp b/src/lib/component/controller/GraphController.cpp index 0ce3aec5..05a9e99b 100644 --- a/src/lib/component/controller/GraphController.cpp +++ b/src/lib/component/controller/GraphController.cpp @@ -326,6 +326,14 @@ void GraphController::handleMessage(MessageDeactivateEdge* message) getView()->activateEdge(0); } +void GraphController::handleMessage(MessageFocusChanged* message) +{ + if (message->isReplayed() && message->isFromGraph()) + { + m_tokenIdToFocus = message->tokenOrLocationId; + } +} + void GraphController::handleMessage(MessageFlushUpdates* message) { GraphView::GraphParams params; @@ -344,12 +352,12 @@ void GraphController::handleMessage(MessageScrollGraph* message) void GraphController::handleMessage(MessageFocusIn* message) { - getView()->focusTokenIds(message->tokenIds); + getView()->coFocusTokenIds(message->tokenIds); } void GraphController::handleMessage(MessageFocusOut* message) { - getView()->defocusTokenIds(message->tokenIds); + getView()->deCoFocusTokenIds(message->tokenIds); } void GraphController::handleMessage(MessageGraphNodeBundleSplit* message) @@ -635,6 +643,7 @@ void GraphController::clear() m_useBezierEdges = false; m_showsLegend = false; + m_tokenIdToFocus = 0; getView()->clear(); } @@ -2353,8 +2362,11 @@ void GraphController::buildGraph(MessageBase* message, GraphView::GraphParams pa params.isIndexedList = params.scrollToTop; params.bezierEdges = m_useBezierEdges; params.disableInteraction = m_showsLegend; + params.tokenIdToFocus = m_tokenIdToFocus; getView()->rebuildGraph(m_graph, m_dummyNodes, m_dummyEdges, params); + + m_tokenIdToFocus = 0; } } diff --git a/src/lib/component/controller/GraphController.h b/src/lib/component/controller/GraphController.h index 606888c7..40cb4252 100644 --- a/src/lib/component/controller/GraphController.h +++ b/src/lib/component/controller/GraphController.h @@ -12,6 +12,7 @@ #include "MessageActivateTrail.h" #include "MessageActivateTrailEdge.h" #include "MessageDeactivateEdge.h" +#include "MessageFocusChanged.h" #include "MessageFlushUpdates.h" #include "MessageFocusIn.h" #include "MessageFocusOut.h" @@ -42,6 +43,7 @@ class GraphController , public MessageListener , public MessageListener , public MessageListener + , public MessageListener , public MessageListener , public MessageListener , public MessageListener @@ -67,6 +69,7 @@ private: void handleMessage(MessageActivateTrail* message) override; void handleMessage(MessageActivateTrailEdge* message) override; void handleMessage(MessageDeactivateEdge* message) override; + void handleMessage(MessageFocusChanged* message) override; void handleMessage(MessageFlushUpdates* message) override; void handleMessage(MessageFocusIn* message) override; void handleMessage(MessageFocusOut* message) override; @@ -170,6 +173,7 @@ private: bool m_useBezierEdges = false; bool m_showsLegend = false; + Id m_tokenIdToFocus = 0; }; #endif // GRAPH_CONTROLLER_H diff --git a/src/lib/component/controller/TabsController.cpp b/src/lib/component/controller/TabsController.cpp index a66ff6ed..e8e73cdf 100644 --- a/src/lib/component/controller/TabsController.cpp +++ b/src/lib/component/controller/TabsController.cpp @@ -5,6 +5,7 @@ #include "MessageIndexingFinished.h" #include "MessageScrollToLine.h" #include "MessageSearch.h" +#include "MessageWindowChanged.h" #include "ScreenSearchInterfaces.h" #include "TabId.h" #include "TaskLambda.h" @@ -52,6 +53,8 @@ void TabsController::addTab(Id tabId, SearchMatch match) m_tabs.emplace( tabId, std::make_shared(tabId, m_viewFactory, m_storageAccess, m_screenSearchSender)); + MessageWindowChanged().dispatch(); + if (match.isValid()) { MessageSearch msg({match}, NodeTypeSet::all()); diff --git a/src/lib/component/controller/TooltipController.cpp b/src/lib/component/controller/TooltipController.cpp index fa2ed3b9..97cdbb59 100644 --- a/src/lib/component/controller/TooltipController.cpp +++ b/src/lib/component/controller/TooltipController.cpp @@ -42,7 +42,7 @@ void TooltipController::handleMessage(MessageActivateLocalSymbols* message) void TooltipController::handleMessage(MessageFocusIn* message) { - if (!message->tokenIds.size()) + if (!message->tokenIds.size() || message->origin == TOOLTIP_ORIGIN_NONE) { return; } diff --git a/src/lib/component/controller/UndoRedoController.cpp b/src/lib/component/controller/UndoRedoController.cpp index cce14089..29406e8d 100644 --- a/src/lib/component/controller/UndoRedoController.cpp +++ b/src/lib/component/controller/UndoRedoController.cpp @@ -183,6 +183,19 @@ void UndoRedoController::handleMessage(MessageDeactivateEdge* message) processCommand(command); } +void UndoRedoController::handleMessage(MessageFocusChanged* message) +{ + if (sameMessageTypeAsLast(message) && + static_cast(lastMessage())->tokenOrLocationId == + message->tokenOrLocationId) + { + return; + } + + Command command(std::make_shared(*message), Command::ORDER_VIEW, true); + processCommand(command); +} + void UndoRedoController::handleMessage(MessageGraphNodeBundleSplit* message) { Command command(std::make_shared(*message), Command::ORDER_ADAPT); diff --git a/src/lib/component/controller/UndoRedoController.h b/src/lib/component/controller/UndoRedoController.h index 86695292..515ae651 100644 --- a/src/lib/component/controller/UndoRedoController.h +++ b/src/lib/component/controller/UndoRedoController.h @@ -15,6 +15,7 @@ #include "MessageChangeFileView.h" #include "MessageCodeShowDefinition.h" #include "MessageDeactivateEdge.h" +#include "MessageFocusChanged.h" #include "MessageGraphNodeBundleSplit.h" #include "MessageGraphNodeExpand.h" #include "MessageGraphNodeHide.h" @@ -49,6 +50,7 @@ class UndoRedoController , public MessageListener , public MessageListener , public MessageListener + , public MessageListener , public MessageListener , public MessageListener , public MessageListener @@ -102,6 +104,7 @@ private: void handleMessage(MessageChangeFileView* message) override; void handleMessage(MessageCodeShowDefinition* message) override; void handleMessage(MessageDeactivateEdge* message) override; + void handleMessage(MessageFocusChanged* message) override; void handleMessage(MessageGraphNodeBundleSplit* message) override; void handleMessage(MessageGraphNodeExpand* message) override; void handleMessage(MessageGraphNodeHide* message) override; diff --git a/src/lib/component/view/CodeView.h b/src/lib/component/view/CodeView.h index a0dc8728..dac3b8c6 100644 --- a/src/lib/component/view/CodeView.h +++ b/src/lib/component/view/CodeView.h @@ -32,6 +32,7 @@ public: { bool clearSnippets = false; bool useSingleFileCache = true; + Id locationIdToFocus = 0; size_t referenceCount = 0; size_t referenceIndex = 0; @@ -68,14 +69,17 @@ public: virtual bool showsErrors() const = 0; - virtual void focusTokenIds(const std::vector& focusedTokenIds) = 0; - virtual void defocusTokenIds() = 0; + virtual void coFocusTokenIds(const std::vector& coFocusedTokenIds) = 0; + virtual void deCoFocusTokenIds() = 0; virtual bool isInListMode() const = 0; virtual void setMode(bool listMode) = 0; virtual bool hasSingleFileCached(const FilePath& filePath) const = 0; + virtual void setNavigationFocus(bool focus) = 0; + virtual bool hasNavigationFocus() const = 0; + protected: CodeController* getController(); }; diff --git a/src/lib/component/view/CompositeView.cpp b/src/lib/component/view/CompositeView.cpp index 7682fe94..c60eeab9 100644 --- a/src/lib/component/view/CompositeView.cpp +++ b/src/lib/component/view/CompositeView.cpp @@ -2,13 +2,18 @@ #include -CompositeView::CompositeView(ViewLayout* viewLayout, CompositeDirection direction, const std::string& name) - : View(viewLayout), m_direction(direction), m_name(name) +CompositeView::CompositeView(ViewLayout* viewLayout, CompositeDirection direction, const std::string& name, Id tabId) + : View(viewLayout), m_direction(direction), m_name(name), m_tabId(tabId) { } CompositeView::~CompositeView() {} +Id CompositeView::getSchedulerId() const +{ + return m_tabId; +} + CompositeView::CompositeDirection CompositeView::getDirection() const { return m_direction; @@ -50,3 +55,8 @@ void CompositeView::setViewEnabled(View* view, bool enabled) { getViewLayout()->setViewEnabled(view, enabled); } + +void CompositeView::handleMessage(MessageFocusedSearchView* message) +{ + showFocusIndicator(message->focusIn); +} diff --git a/src/lib/component/view/CompositeView.h b/src/lib/component/view/CompositeView.h index a8c51ec2..44908fd8 100644 --- a/src/lib/component/view/CompositeView.h +++ b/src/lib/component/view/CompositeView.h @@ -3,12 +3,15 @@ #include +#include "MessageFocusedSearchView.h" +#include "MessageListener.h" #include "View.h" #include "ViewLayout.h" class CompositeView : public View , public ViewLayout + , public MessageListener { public: enum CompositeDirection @@ -17,30 +20,37 @@ public: DIRECTION_VERTICAL }; - CompositeView(ViewLayout* viewLayout, CompositeDirection direction, const std::string& name); + CompositeView(ViewLayout* viewLayout, CompositeDirection direction, const std::string& name, Id tabId); virtual ~CompositeView(); + Id getSchedulerId() const override; + CompositeDirection getDirection() const; const std::vector& getViews() const; virtual void addViewWidget(View* view) = 0; + virtual void showFocusIndicator(bool focus) = 0; + // View implementation - virtual std::string getName() const; + virtual std::string getName() const override; // ViewLayout implementation - virtual void addView(View* view); - virtual void removeView(View* view); + void addView(View* view) override; + void removeView(View* view) override; - virtual void showView(View* view); - virtual void hideView(View* view); + void showView(View* view) override; + void hideView(View* view) override; - virtual void setViewEnabled(View* view, bool enabled); + void setViewEnabled(View* view, bool enabled) override; private: + void handleMessage(MessageFocusedSearchView* message) override; + std::vector m_views; CompositeDirection m_direction; - std::string m_name; + const std::string m_name; + const Id m_tabId; }; #endif // COMPOSITE_VIEW_H diff --git a/src/lib/component/view/GraphView.h b/src/lib/component/view/GraphView.h index cbb99297..9888facf 100644 --- a/src/lib/component/view/GraphView.h +++ b/src/lib/component/view/GraphView.h @@ -29,6 +29,7 @@ public: bool isIndexedList = false; bool bezierEdges = false; bool disableInteraction = false; + Id tokenIdToFocus = 0; }; GraphView(ViewLayout* viewLayout); @@ -43,8 +44,8 @@ public: const GraphParams params) = 0; virtual void clear() = 0; - virtual void focusTokenIds(const std::vector& focusedTokenIds) = 0; - virtual void defocusTokenIds(const std::vector& defocusedTokenIds) = 0; + virtual void coFocusTokenIds(const std::vector& focusedTokenIds) = 0; + virtual void deCoFocusTokenIds(const std::vector& defocusedTokenIds) = 0; virtual void resizeView() = 0; @@ -54,6 +55,9 @@ public: virtual void scrollToValues(int xValue, int yValue) = 0; virtual void activateEdge(Id edgeId) = 0; + + virtual void setNavigationFocus(bool focus) = 0; + virtual bool hasNavigationFocus() const = 0; }; #endif // GRAPH_VIEW_H diff --git a/src/lib/component/view/GraphViewStyle.cpp b/src/lib/component/view/GraphViewStyle.cpp index aeb48ac7..d80c6755 100644 --- a/src/lib/component/view/GraphViewStyle.cpp +++ b/src/lib/component/view/GraphViewStyle.cpp @@ -19,6 +19,7 @@ int GraphViewStyle::s_fontSize; std::string GraphViewStyle::s_fontName; float GraphViewStyle::s_zoomFactor; +std::string GraphViewStyle::s_focusColor; std::map GraphViewStyle::s_nodeColors; std::map GraphViewStyle::s_edgeColors; std::map GraphViewStyle::s_screenMatchColors; @@ -141,6 +142,7 @@ void GraphViewStyle::loadStyleSettings() s_charWidths.clear(); s_charHeights.clear(); + s_focusColor.clear(); s_nodeColors.clear(); s_edgeColors.clear(); s_screenMatchColors.clear(); @@ -369,7 +371,7 @@ GraphViewStyle::NodeMargins GraphViewStyle::getMarginsOfGroupNode(GroupType type } GraphViewStyle::NodeStyle GraphViewStyle::getStyleForNodeType( - NodeType type, bool defined, bool isActive, bool isFocused, bool hasChildren, bool hasQualifier) + NodeType type, bool defined, bool isActive, bool isFocused, bool isCoFocused, bool hasChildren, bool hasQualifier) { return getStyleForNodeType( type.getNodeStyle(), @@ -378,6 +380,7 @@ GraphViewStyle::NodeStyle GraphViewStyle::getStyleForNodeType( defined, isActive, isFocused, + isCoFocused, hasChildren, hasQualifier); } @@ -389,17 +392,18 @@ GraphViewStyle::NodeStyle GraphViewStyle::getStyleForNodeType( bool defined, bool isActive, bool isFocused, + bool isCoFocused, bool hasChildren, bool hasQualifier) { NodeStyle style; - style.color = getNodeColor(underscoredTypeString, isActive || isFocused); + style.color = getNodeColor(underscoredTypeString, isActive || isCoFocused); style.fontName = getFontNameForDataNode(); style.fontSize = getFontSizeForStyleType(type); - if (isActive || isFocused) + if (isActive || isCoFocused) { style.fontBold = true; } @@ -485,6 +489,12 @@ GraphViewStyle::NodeStyle GraphViewStyle::getStyleForNodeType( } } + if (isFocused) + { + style.color.border = getFocusColor(); + style.borderWidth = 3; + } + return style; } @@ -541,6 +551,7 @@ GraphViewStyle::NodeStyle GraphViewStyle::getStyleOfBundleNode(bool isFocused) true, false, isFocused, + isFocused, false, false); } @@ -570,7 +581,7 @@ GraphViewStyle::NodeStyle GraphViewStyle::getStyleOfTextNode(int fontSizeDiff) return style; } -GraphViewStyle::NodeStyle GraphViewStyle::getStyleOfGroupNode(GroupType type, bool isFocused) +GraphViewStyle::NodeStyle GraphViewStyle::getStyleOfGroupNode(GroupType type, bool isCoFocused) { NodeStyle style; @@ -594,7 +605,7 @@ GraphViewStyle::NodeStyle GraphViewStyle::getStyleOfGroupNode(GroupType type, bo { colorType += "inheritance"; - if (isFocused) + if (isCoFocused) { style.borderWidth = 3; } @@ -604,7 +615,7 @@ GraphViewStyle::NodeStyle GraphViewStyle::getStyleOfGroupNode(GroupType type, bo return style; } - style.color = getNodeColor(colorType, isFocused); + style.color = getNodeColor(colorType, isCoFocused); style.fontName = getFontNameOfGroupNode(); style.fontSize = getFontSizeOfGroupNode(); @@ -621,12 +632,14 @@ GraphViewStyle::EdgeStyle GraphViewStyle::getStyleForEdgeType( { EdgeStyle style; - style.width = isActive ? 4.0f : 2.0f; - style.zValue = isActive ? 5 : 2; + bool active = isActive || isFocused; + + style.width = active ? 4.0f : 2.0f; + style.zValue = active ? 5 : 2; if (isTrailEdge) { - style.zValue = isActive ? 5 : 2; + style.zValue = active ? 5 : 2; } style.arrowLength = 5; @@ -641,8 +654,14 @@ GraphViewStyle::EdgeStyle GraphViewStyle::getStyleForEdgeType( style.originOffset.y = 5; style.targetOffset.y = -5; - style.color = getEdgeColor( - utility::encodeToUtf8(Edge::getUnderscoredTypeString(type)), isActive || isFocused); + if (isFocused) + { + style.color = getFocusColor(); + } + else + { + style.color = getEdgeColor(utility::encodeToUtf8(Edge::getUnderscoredTypeString(type))); + } switch (type) { @@ -655,7 +674,7 @@ GraphViewStyle::EdgeStyle GraphViewStyle::getStyleForEdgeType( style.originOffset.y = 0; style.targetOffset.y = 0; style.verticalOffset = 0; - style.zValue = isActive ? 1 : -5; + style.zValue = active ? 1 : -5; break; case Edge::EDGE_CALL: @@ -666,10 +685,7 @@ GraphViewStyle::EdgeStyle GraphViewStyle::getStyleForEdgeType( if (isTrailEdge && isActive) { style.width = 3; - style.color = ColorScheme::getInstance()->getColor( - "graph/edge/" + utility::encodeToUtf8(Edge::getUnderscoredTypeString(type)) + - "/trail_focus", - style.color); + style.color = ColorScheme::getInstance()->getColor("graph/edge/call_trail_focus", style.color); } break; @@ -690,17 +706,17 @@ GraphViewStyle::EdgeStyle GraphViewStyle::getStyleForEdgeType( style.targetOffset.y = -10; style.verticalOffset = 0; style.cornerRadius = 7; - style.zValue = isActive ? 2 : -3; - style.width = isActive ? 3.0f : 2.0f; + style.zValue = active ? 2 : -3; + style.width = active ? 3.0f : 2.0f; if (isTrailEdge) { - style.zValue = isActive ? 2 : -20; + style.zValue = active ? 2 : -20; } break; case Edge::EDGE_TEMPLATE_SPECIALIZATION: - style.zValue = isActive ? 2 : -3; + style.zValue = active ? 2 : -3; style.arrowLength = 10; style.arrowWidth = 13; style.arrowClosed = true; @@ -708,7 +724,7 @@ GraphViewStyle::EdgeStyle GraphViewStyle::getStyleForEdgeType( break; case Edge::EDGE_INCLUDE: - style.zValue = isActive ? 2 : -3; + style.zValue = active ? 2 : -3; case Edge::EDGE_MACRO_USAGE: style.originOffset.y = 0; style.targetOffset.y = 0; @@ -755,9 +771,19 @@ float GraphViewStyle::getZoomFactor() return s_zoomFactor; } -const GraphViewStyle::NodeColor& GraphViewStyle::getNodeColor(const std::string& typeStr, bool focus) +const std::string& GraphViewStyle::getFocusColor() { - std::string type = focus ? typeStr + "focus" : typeStr; + if (s_focusColor.empty()) + { + s_focusColor = ColorScheme::getInstance()->getColor("window/focus"); + } + + return s_focusColor; +} + +const GraphViewStyle::NodeColor& GraphViewStyle::getNodeColor(const std::string& typeStr, bool highlight) +{ + std::string type = highlight ? typeStr + "highlight" : typeStr; std::map::const_iterator it = s_nodeColors.find(type); if (it != s_nodeColors.end()) @@ -767,33 +793,27 @@ const GraphViewStyle::NodeColor& GraphViewStyle::getNodeColor(const std::string& NodeColor color; ColorScheme* scheme = ColorScheme::getInstance().get(); - ColorScheme::ColorState state = focus ? ColorScheme::FOCUS : ColorScheme::NORMAL; - color.fill = scheme->getNodeTypeColor(typeStr, "fill", state); - color.border = scheme->getNodeTypeColor(typeStr, "border", state); - color.text = scheme->getNodeTypeColor(typeStr, "text", state); - color.icon = scheme->getNodeTypeColor(typeStr, "icon", state); - color.hatching = scheme->getNodeTypeColor(typeStr, "hatching", state); + color.fill = scheme->getNodeTypeColor(typeStr, "fill", highlight); + color.border = scheme->getNodeTypeColor(typeStr, "border", highlight); + color.text = scheme->getNodeTypeColor(typeStr, "text", highlight); + color.icon = scheme->getNodeTypeColor(typeStr, "icon", highlight); + color.hatching = scheme->getNodeTypeColor(typeStr, "hatching", highlight); s_nodeColors.emplace(type, color); return s_nodeColors.find(type)->second; } -const std::string& GraphViewStyle::getEdgeColor(const std::string& typeStr, bool focus) +const std::string& GraphViewStyle::getEdgeColor(const std::string& type) { - std::string type = focus ? typeStr + "focus" : typeStr; std::map::const_iterator it = s_edgeColors.find(type); - if (it != s_edgeColors.end()) { return it->second; } - ColorScheme* scheme = ColorScheme::getInstance().get(); - ColorScheme::ColorState state = focus ? ColorScheme::FOCUS : ColorScheme::NORMAL; - std::string color = scheme->getEdgeTypeColor(typeStr, state); - + std::string color = ColorScheme::getInstance()->getEdgeTypeColor(type); s_edgeColors.emplace(type, color); return s_edgeColors.find(type)->second; diff --git a/src/lib/component/view/GraphViewStyle.h b/src/lib/component/view/GraphViewStyle.h index a672efc6..bf09846b 100644 --- a/src/lib/component/view/GraphViewStyle.h +++ b/src/lib/component/view/GraphViewStyle.h @@ -121,14 +121,20 @@ public: static NodeMargins getMarginsOfGroupNode(GroupType type, bool hasName); static NodeStyle getStyleForNodeType( - NodeType type, bool defined, bool isActive, bool isFocused, bool hasChildren, bool hasQualifier); + NodeType type, + bool defined, + bool isActive, + bool isFocused, + bool isCoFocused, + bool hasChildren, + bool hasQualifier); static NodeStyle getStyleOfAccessNode(); static NodeStyle getStyleOfExpandToggleNode(); static NodeStyle getStyleOfCountCircle(); static NodeStyle getStyleOfBundleNode(bool isFocused); static NodeStyle getStyleOfQualifier(); static NodeStyle getStyleOfTextNode(int fontSizeDiff); - static NodeStyle getStyleOfGroupNode(GroupType type, bool isFocused); + static NodeStyle getStyleOfGroupNode(GroupType type, bool isCoFocused); static EdgeStyle getStyleForEdgeType( Edge::EdgeType type, bool isActive, bool isFocused, bool isTrailEdge, bool isAmbiguous); @@ -139,8 +145,9 @@ public: static float getZoomFactor(); - static const NodeColor& getNodeColor(const std::string& typeStr, bool focus); - static const std::string& getEdgeColor(const std::string& typeStr, bool focus); + static const std::string& getFocusColor(); + static const NodeColor& getNodeColor(const std::string& typeStr, bool highlight); + static const std::string& getEdgeColor(const std::string& type); static const NodeColor& getScreenMatchColor(bool focus); static int s_gridCellSize; @@ -154,6 +161,7 @@ private: bool defined, bool isActive, bool isFocused, + bool isCoFocused, bool hasChildren, bool hasQualifier); @@ -172,6 +180,7 @@ private: static std::string s_fontName; static float s_zoomFactor; + static std::string s_focusColor; static std::map s_nodeColors; static std::map s_edgeColors; static std::map s_screenMatchColors; diff --git a/src/lib/component/view/ViewFactory.h b/src/lib/component/view/ViewFactory.h index 533c3752..870e1645 100644 --- a/src/lib/component/view/ViewFactory.h +++ b/src/lib/component/view/ViewFactory.h @@ -35,7 +35,8 @@ public: virtual std::shared_ptr createCompositeView( ViewLayout* viewLayout, CompositeView::CompositeDirection direction, - const std::string& name) const = 0; + const std::string& name, + const Id tabId) const = 0; virtual std::shared_ptr createTabbedView( ViewLayout* viewLayout, const std::string& name) const = 0; diff --git a/src/lib/component/view/helper/CodeScrollParams.h b/src/lib/component/view/helper/CodeScrollParams.h index fd2486b6..f9ea8493 100644 --- a/src/lib/component/view/helper/CodeScrollParams.h +++ b/src/lib/component/view/helper/CodeScrollParams.h @@ -22,57 +22,63 @@ struct CodeScrollParams TOP }; - static CodeScrollParams toReference(const FilePath& filePath, Id locationId, Target target) + static CodeScrollParams toReference(const FilePath& filePath, Id locationId, Id scopeLocationId, Target target) { - return CodeScrollParams(Type::TO_REFERENCE, target, filePath, locationId, 0, 0, false); + return CodeScrollParams(Type::TO_REFERENCE, target, filePath, locationId, scopeLocationId, 0, 0, false); } static CodeScrollParams toFile(const FilePath& filePath, Target target) { - return CodeScrollParams(Type::TO_LINE, target, filePath, 0, 0, 0, false); + return CodeScrollParams(Type::TO_LINE, target, filePath, 0, 0, 0, 0, false); } static CodeScrollParams toLine(const FilePath& filePath, size_t line, Target target) { - return CodeScrollParams(Type::TO_LINE, target, filePath, 0, line, 0, false); + return CodeScrollParams(Type::TO_LINE, target, filePath, 0, 0, line, 0, false); } static CodeScrollParams toValue(size_t value, bool inListMode) { - return CodeScrollParams(Type::TO_VALUE, Target::VISIBLE, FilePath(), 0, 0, value, inListMode); + return CodeScrollParams(Type::TO_VALUE, Target::VISIBLE, FilePath(), 0, 0, 0, value, inListMode); } CodeScrollParams( - Type type, Target target, FilePath filePath, Id locationId, size_t line, size_t value, bool inListMode) + Type type, + Target target, + FilePath filePath, + Id locationId, + Id scopeLocationId, + size_t line, + size_t value, + bool inListMode + ) : type(type) , target(target) , filePath(filePath) , locationId(locationId) + , scopeLocationId(scopeLocationId) , line(line) , value(value) , inListMode(inListMode) { } - CodeScrollParams() - : type(Type::NONE), target(Target::VISIBLE), locationId(0), line(0), value(0), inListMode(false) - { - } + CodeScrollParams() {} - - Type type; - Target target; + Type type = Type::NONE; + Target target = Target::VISIBLE; FilePath filePath; // Reference - Id locationId; + Id locationId = 0; + Id scopeLocationId = 0; // Line - size_t line; + size_t line = 0; // Value - size_t value; + size_t value = 0; bool inListMode = false; }; diff --git a/src/lib/data/storage/PersistentStorage.cpp b/src/lib/data/storage/PersistentStorage.cpp index c278ff44..c595a17f 100644 --- a/src/lib/data/storage/PersistentStorage.cpp +++ b/src/lib/data/storage/PersistentStorage.cpp @@ -2220,7 +2220,7 @@ TooltipInfo PersistentStorage::getTooltipInfoForTokenIds( { info.offset = Vec2i(20, 30); } - else + else if (origin == TOOLTIP_ORIGIN_GRAPH) { info.offset = Vec2i(50, 20); } diff --git a/src/lib/data/tooltip/TooltipOrigin.h b/src/lib/data/tooltip/TooltipOrigin.h index 7196a46e..2c3d4179 100644 --- a/src/lib/data/tooltip/TooltipOrigin.h +++ b/src/lib/data/tooltip/TooltipOrigin.h @@ -3,6 +3,7 @@ enum TooltipOrigin { + TOOLTIP_ORIGIN_NONE, TOOLTIP_ORIGIN_GRAPH, TOOLTIP_ORIGIN_CODE }; diff --git a/src/lib/settings/ColorScheme.cpp b/src/lib/settings/ColorScheme.cpp index 8d3a6505..098aa0f8 100644 --- a/src/lib/settings/ColorScheme.cpp +++ b/src/lib/settings/ColorScheme.cpp @@ -31,69 +31,57 @@ std::string ColorScheme::getColor(const std::string& key, const std::string& def return getValue(key, defaultColor); } -std::string ColorScheme::getNodeTypeColor(NodeType type, const std::string& key, ColorState state) const +std::string ColorScheme::getNodeTypeColor(NodeType type, const std::string& key, bool highlight) const { - return getNodeTypeColor(type.getUnderscoredTypeString(), key, state); + return getNodeTypeColor(type.getUnderscoredTypeString(), key, highlight); } std::string ColorScheme::getNodeTypeColor( - const std::string& typeStr, const std::string& key, ColorState state) const + const std::string& typeStr, const std::string& key, bool highlight) const { disableWarnings(); const std::string type = getValue("graph/node/" + typeStr + "/like", typeStr); std::string color = getValue( - "graph/node/" + type + "/" + key + "/" + stateToString(state), ""); + "graph/node/" + type + "/" + key + "/" + (highlight ? "highlight" : "normal"), ""); - if (!color.size() && state != NORMAL) + if (!color.size() && highlight) { - color = getValue( - "graph/node/" + type + "/" + key + "/" + stateToString(NORMAL), ""); + color = getValue("graph/node/" + type + "/" + key + "/normal", ""); } if (!color.size()) { - color = getValue("graph/node/default/" + key + "/" + stateToString(state), ""); + color = getValue( + "graph/node/default/" + key + "/" + (highlight ? "highlight" : "normal"), ""); } enableWarnings(); - if (!color.size() && state != NORMAL) + if (!color.size() && highlight) { - color = getValue( - "graph/node/default/" + key + "/" + stateToString(NORMAL), "#FFFFFF"); + color = getValue("graph/node/default/" + key + "/normal", "#FF1493"); } return color; } -std::string ColorScheme::getEdgeTypeColor(Edge::EdgeType type, ColorState state) const +std::string ColorScheme::getEdgeTypeColor(Edge::EdgeType type) const { - return getEdgeTypeColor(utility::encodeToUtf8(Edge::getUnderscoredTypeString(type)), state); + return getEdgeTypeColor(utility::encodeToUtf8(Edge::getUnderscoredTypeString(type))); } -std::string ColorScheme::getEdgeTypeColor(const std::string& typeStr, ColorState state) const +std::string ColorScheme::getEdgeTypeColor(const std::string& type) const { disableWarnings(); - std::string type = getValue("graph/edge/" + typeStr + "/like", typeStr); - std::string color = getValue("graph/edge/" + type + "/" + stateToString(state), ""); - - if (!color.size() && state != NORMAL) - { - color = getValue("graph/edge/" + type + "/" + stateToString(NORMAL), ""); - } - - if (!color.size()) - { - color = getValue("graph/edge/default/" + stateToString(state), ""); - } + std::string color = getValue("graph/edge/" + type, ""); enableWarnings(); - if (!color.size() && state != NORMAL) + if (!color.size()) { - color = getValue("graph/edge/default/" + stateToString(NORMAL), "#FFFFFF"); + color = getValue("graph/edge/default", "#FF1493"); } return color; @@ -103,12 +91,12 @@ std::string ColorScheme::getSearchTypeColor( const std::string& searchTypeName, const std::string& key, const std::string& state) const { std::string path = "search/query/" + searchTypeName + "/" + state + "/" + key; - return getValue(path, "#FFFFFF"); + return getValue(path, "#FF1493"); } std::string ColorScheme::getSyntaxColor(const std::string& key) const { - return getValue("code/snippet/syntax/" + key, "#FFFFFF"); + return getValue("code/snippet/syntax/" + key, "#FF1493"); } std::string ColorScheme::getCodeAnnotationTypeColor( diff --git a/src/lib/settings/ColorScheme.h b/src/lib/settings/ColorScheme.h index 120fde21..ae6ddef1 100644 --- a/src/lib/settings/ColorScheme.h +++ b/src/lib/settings/ColorScheme.h @@ -23,12 +23,11 @@ public: std::string getColor(const std::string& key) const; std::string getColor(const std::string& key, const std::string& defaultColor) const; - std::string getNodeTypeColor(NodeType type, const std::string& key, ColorState state) const; - std::string getNodeTypeColor( - const std::string& typeStr, const std::string& key, ColorState state) const; + std::string getNodeTypeColor(NodeType type, const std::string& key, bool highlight) const; + std::string getNodeTypeColor(const std::string& typeStr, const std::string& key, bool highlight) const; - std::string getEdgeTypeColor(Edge::EdgeType type, ColorState state) const; - std::string getEdgeTypeColor(const std::string& typeStr, ColorState state) const; + std::string getEdgeTypeColor(Edge::EdgeType type) const; + std::string getEdgeTypeColor(const std::string& type) const; std::string getSearchTypeColor( const std::string& searchTypeName, diff --git a/src/lib/utility/messaging/type/MessageWindowChanged.h b/src/lib/utility/messaging/type/MessageWindowChanged.h new file mode 100644 index 00000000..669e03b2 --- /dev/null +++ b/src/lib/utility/messaging/type/MessageWindowChanged.h @@ -0,0 +1,15 @@ +#ifndef MESSAGE_WINDOW_CHANGED_H +#define MESSAGE_WINDOW_CHANGED_H + +#include "Message.h" + +class MessageWindowChanged: public Message +{ +public: + static const std::string getStaticType() + { + return "MessageWindowChanged"; + } +}; + +#endif // MESSAGE_WINDOW_CHANGED_H diff --git a/src/lib/utility/messaging/type/code/MessageActivateLocalSymbols.h b/src/lib/utility/messaging/type/code/MessageActivateLocalSymbols.h index 934cb93a..40c77fa8 100644 --- a/src/lib/utility/messaging/type/code/MessageActivateLocalSymbols.h +++ b/src/lib/utility/messaging/type/code/MessageActivateLocalSymbols.h @@ -8,11 +8,6 @@ class MessageActivateLocalSymbols: public Message { public: - MessageActivateLocalSymbols() - { - setSchedulerId(TabId::currentTab()); - } - MessageActivateLocalSymbols(const std::vector& symbolIds): symbolIds(symbolIds) { setSchedulerId(TabId::currentTab()); diff --git a/src/lib/utility/messaging/type/code/MessageToNextCodeReference.h b/src/lib/utility/messaging/type/code/MessageToNextCodeReference.h new file mode 100644 index 00000000..a635f47c --- /dev/null +++ b/src/lib/utility/messaging/type/code/MessageToNextCodeReference.h @@ -0,0 +1,40 @@ +#ifndef MESSAGE_TO_NEXT_CODE_REFERENCE_H +#define MESSAGE_TO_NEXT_CODE_REFERENCE_H + +#include "Message.h" +#include "TabId.h" + +class MessageToNextCodeReference: public Message +{ +public: + MessageToNextCodeReference(const FilePath& filePath, size_t lineNumber, bool next) + : filePath(filePath), lineNumber(lineNumber), next(next) + { + setSchedulerId(TabId::currentTab()); + } + + static const std::string getStaticType() + { + return "MessageToNextCodeReference"; + } + + virtual void print(std::wostream& os) const + { + os << filePath.wstr() << L' ' << lineNumber << L' '; + + if (next) + { + os << L"next"; + } + else + { + os << L"previous"; + } + } + + const FilePath filePath; + const size_t lineNumber; + const bool next; +}; + +#endif // MESSAGE_TO_NEXT_CODE_REFERENCE_H diff --git a/src/lib/utility/messaging/type/focus/MessageFocusChanged.h b/src/lib/utility/messaging/type/focus/MessageFocusChanged.h new file mode 100644 index 00000000..4ff8f477 --- /dev/null +++ b/src/lib/utility/messaging/type/focus/MessageFocusChanged.h @@ -0,0 +1,58 @@ +#ifndef MESSAGE_FOCUS_CHANGED_H +#define MESSAGE_FOCUS_CHANGED_H + +#include "Message.h" +#include "TabId.h" + +class MessageFocusChanged: public Message +{ +public: + enum class ViewType + { + GRAPH, + CODE + }; + + MessageFocusChanged(ViewType type, Id tokenOrLocationId) + : type(type) + , tokenOrLocationId(tokenOrLocationId) + { + setIsLogged(false); + setSchedulerId(TabId::currentTab()); + } + + static const std::string getStaticType() + { + return "MessageFocusChanged"; + } + + virtual void print(std::wostream& os) const + { + switch (type) + { + case ViewType::GRAPH: + os << "graph"; + break; + case ViewType::CODE: + os << "code"; + break; + } + + os << " " << tokenOrLocationId; + } + + bool isFromGraph() const + { + return type == ViewType::GRAPH; + } + + bool isFromCode() const + { + return type == ViewType::CODE; + } + + const ViewType type; + const Id tokenOrLocationId; +}; + +#endif // MESSAGE_FOCUS_CHANGED_H diff --git a/src/lib/utility/messaging/type/MessageFocusIn.h b/src/lib/utility/messaging/type/focus/MessageFocusIn.h similarity index 95% rename from src/lib/utility/messaging/type/MessageFocusIn.h rename to src/lib/utility/messaging/type/focus/MessageFocusIn.h index 5ba391cd..0ece1d26 100644 --- a/src/lib/utility/messaging/type/MessageFocusIn.h +++ b/src/lib/utility/messaging/type/focus/MessageFocusIn.h @@ -5,14 +5,13 @@ #include "Message.h" #include "TabId.h" -#include "types.h" - #include "TooltipOrigin.h" +#include "types.h" class MessageFocusIn: public Message { public: - MessageFocusIn(const std::vector& tokenIds, TooltipOrigin origin) + MessageFocusIn(const std::vector& tokenIds, TooltipOrigin origin = TOOLTIP_ORIGIN_NONE) : tokenIds(tokenIds), origin(origin) { setIsLogged(false); diff --git a/src/lib/utility/messaging/type/MessageFocusOut.h b/src/lib/utility/messaging/type/focus/MessageFocusOut.h similarity index 100% rename from src/lib/utility/messaging/type/MessageFocusOut.h rename to src/lib/utility/messaging/type/focus/MessageFocusOut.h diff --git a/src/lib/utility/messaging/type/focus/MessageFocusView.h b/src/lib/utility/messaging/type/focus/MessageFocusView.h new file mode 100644 index 00000000..15be0e52 --- /dev/null +++ b/src/lib/utility/messaging/type/focus/MessageFocusView.h @@ -0,0 +1,47 @@ +#ifndef MESSAGE_FOCUS_VIEW_H +#define MESSAGE_FOCUS_VIEW_H + +#include "Message.h" +#include "TabId.h" + +class MessageFocusView: public Message +{ +public: + enum class ViewType + { + GRAPH, + CODE, + TOGGLE + }; + + MessageFocusView(ViewType type): type(type) + { + setIsLogged(false); + setSchedulerId(TabId::currentTab()); + } + + static const std::string getStaticType() + { + return "MessageFocusView"; + } + + virtual void print(std::wostream& os) const + { + switch (type) + { + case ViewType::GRAPH: + os << "graph"; + break; + case ViewType::CODE: + os << "code"; + break; + case ViewType::TOGGLE: + os << "toggle"; + break; + } + } + + const ViewType type; +}; + +#endif // MESSAGE_FOCUS_VIEW_H diff --git a/src/lib/utility/messaging/type/focus/MessageFocusedSearchView.h b/src/lib/utility/messaging/type/focus/MessageFocusedSearchView.h new file mode 100644 index 00000000..ac2149ae --- /dev/null +++ b/src/lib/utility/messaging/type/focus/MessageFocusedSearchView.h @@ -0,0 +1,25 @@ +#ifndef MESSAGE_FOCUSED_SEARCH_VIEW_H +#define MESSAGE_FOCUSED_SEARCH_VIEW_H + +#include "Message.h" +#include "TabId.h" + +class MessageFocusedSearchView: public Message +{ +public: + MessageFocusedSearchView(bool focusIn) + : focusIn(focusIn) + { + setIsLogged(false); + setSchedulerId(TabId::currentTab()); + } + + static const std::string getStaticType() + { + return "MessageFocusedSearchView"; + } + + const bool focusIn; +}; + +#endif // MESSAGE_FOCUSED_SEARCH_VIEW_H diff --git a/src/lib/utility/utility.h b/src/lib/utility/utility.h index eac71e06..aa967840 100644 --- a/src/lib/utility/utility.h +++ b/src/lib/utility/utility.h @@ -5,6 +5,7 @@ #include #include #include +#include #include #include #include @@ -49,6 +50,9 @@ std::vector toVector(const std::deque& d); template std::vector toVector(const std::set& d); +template +std::vector toVector(const std::list& d); + template std::set toSet(const std::vector& d); @@ -242,6 +246,15 @@ std::vector utility::toVector(const std::set& d) return v; } +template +std::vector utility::toVector(const std::list& d) +{ + std::vector v; + v.reserve(d.size()); + v.insert(v.begin(), d.begin(), d.end()); + return v; +} + template std::set utility::toSet(const std::vector& v) { diff --git a/src/lib_gui/CMakeLists.txt b/src/lib_gui/CMakeLists.txt index e9a545f5..1171705e 100644 --- a/src/lib_gui/CMakeLists.txt +++ b/src/lib_gui/CMakeLists.txt @@ -21,6 +21,8 @@ add_files( qt/element/button/QtSelfRefreshIconButton.cpp qt/element/button/QtSelfRefreshIconButton.h + qt/element/code/CodeFocusHandler.cpp + qt/element/code/CodeFocusHandler.h qt/element/code/QtCodeArea.cpp qt/element/code/QtCodeArea.h qt/element/code/QtCodeField.cpp @@ -94,8 +96,6 @@ add_files( qt/graphics/base/QtCountCircleItem.cpp qt/graphics/base/QtCountCircleItem.h - qt/graphics/base/QtGraphicsView.cpp - qt/graphics/base/QtGraphicsView.h qt/graphics/base/QtLineItemAngled.cpp qt/graphics/base/QtLineItemAngled.h qt/graphics/base/QtLineItemBase.cpp @@ -133,6 +133,11 @@ add_files( qt/graphics/graph/QtGraphNodeText.cpp qt/graphics/graph/QtGraphNodeText.h + qt/graphics/GraphFocusHandler.cpp + qt/graphics/GraphFocusHandler.h + qt/graphics/QtGraphicsView.cpp + qt/graphics/QtGraphicsView.h + qt/network/QtIDECommunicationController.cpp qt/network/QtIDECommunicationController.h qt/network/QtNetworkFactory.cpp diff --git a/src/lib_gui/qt/element/code/CodeFocusHandler.cpp b/src/lib_gui/qt/element/code/CodeFocusHandler.cpp new file mode 100644 index 00000000..edcda969 --- /dev/null +++ b/src/lib_gui/qt/element/code/CodeFocusHandler.cpp @@ -0,0 +1,116 @@ +#include "CodeFocusHandler.h" + +#include "MessageFocusChanged.h" +#include "MessageFocusIn.h" +#include "MessageFocusOut.h" +#include "MessageFocusView.h" + +void CodeFocusHandler::focus() +{ + m_hasFocus = true; + + if (!m_oldFocus.isEmpty()) + { + setCurrentFocus(m_oldFocus, false); + m_oldFocus = Focus(); + } +} + +void CodeFocusHandler::defocus() +{ + m_oldFocus = m_focus; + setCurrentFocus({}, false); + m_hasFocus = false; +} + +bool CodeFocusHandler::isFocused() const +{ + return m_hasFocus; +} + +void CodeFocusHandler::clearCurrentFocus() +{ + setCurrentFocus({}, false); + m_oldFocus = Focus(); + m_targetColumn = 0; +} + +const CodeFocusHandler::Focus& CodeFocusHandler::getCurrentFocus() const +{ + return m_focus; +} + +void CodeFocusHandler::setCurrentFocus(const Focus& focus, bool fromMouse) +{ + if (m_hasFocus) + { + bool same = (m_focus.tokenIds == focus.tokenIds); + if (!fromMouse && !same && m_focus.tokenIds.size()) + { + MessageFocusOut(m_focus.tokenIds).dispatch(); + } + + m_focus = focus; + updateFiles(); + + if (!fromMouse && !same && m_focus.tokenIds.size()) + { + MessageFocusIn(m_focus.tokenIds).dispatch(); + } + + if (focus.locationId) + { + MessageFocusChanged(MessageFocusChanged::ViewType::CODE, focus.locationId).dispatch(); + } + } + else + { + m_oldFocus = focus; + } +} + +bool CodeFocusHandler::hasCurrentFocus() const +{ + if (m_hasFocus) + { + return !m_focus.isEmpty(); + } + else + { + return !m_oldFocus.isEmpty(); + } +} + +void CodeFocusHandler::setFocusedLocationId( + QtCodeArea* area, size_t lineNumber, size_t columnNumber, Id locationId, const std::vector& tokenIds, bool fromMouse) +{ + if (columnNumber) + { + m_targetColumn = columnNumber; + } + + setCurrentFocus({nullptr, area, nullptr, lineNumber, locationId, tokenIds}, fromMouse); +} + +void CodeFocusHandler::setFocusedScopeLine(QtCodeArea* area, QPushButton* scopeLine, size_t lineNumber) +{ + setCurrentFocus({nullptr, area, scopeLine, lineNumber, 0, {}}, false); +} + +void CodeFocusHandler::setFocusedFile(QtCodeFile* file) +{ + setCurrentFocus({file, nullptr, nullptr, 0, 0, {}}, false); +} + +size_t CodeFocusHandler::getTargetColumn() const +{ + return m_targetColumn; +} + +void CodeFocusHandler::focusView() +{ + if (!m_hasFocus) + { + MessageFocusView(MessageFocusView::ViewType::CODE).dispatch(); + } +} diff --git a/src/lib_gui/qt/element/code/CodeFocusHandler.h b/src/lib_gui/qt/element/code/CodeFocusHandler.h new file mode 100644 index 00000000..bb5cbe1c --- /dev/null +++ b/src/lib_gui/qt/element/code/CodeFocusHandler.h @@ -0,0 +1,73 @@ +#ifndef CODE_FOCUS_HANDLER_H +#define CODE_FOCUS_HANDLER_H + +#include + +#include "types.h" + +class QPushButton; +class QtCodeArea; +class QtCodeFile; + +class CodeFocusHandler +{ +public: + enum class Direction + { + UP, + DOWN, + LEFT, + RIGHT + }; + + struct Focus + { + QtCodeFile* file = nullptr; + QtCodeArea* area = nullptr; + QPushButton* scopeLine = nullptr; + size_t lineNumber = 0; + Id locationId = 0; + std::vector tokenIds; + + bool isEmpty() const + { + return file == nullptr && area == nullptr && scopeLine == nullptr && lineNumber == 0 && + locationId == 0 && !tokenIds.size(); + } + }; + + void focus(); + void defocus(); + + bool isFocused() const; + + void clearCurrentFocus(); + const Focus& getCurrentFocus() const; + void setCurrentFocus(const Focus& focus, bool fromMouse); + bool hasCurrentFocus() const; + + void setFocusedLocationId( + QtCodeArea* area, + size_t lineNumber, + size_t columnNumber, + Id locationId, + const std::vector& tokenIds, + bool fromMouse); + void setFocusedScopeLine(QtCodeArea* area, QPushButton* scopeLine, size_t lineNumber); + void setFocusedFile(QtCodeFile* file); + + size_t getTargetColumn() const; + + void focusView(); + + virtual void updateFiles() = 0; + +private: + Focus m_focus; + Focus m_oldFocus; + + size_t m_targetColumn = 0; + bool m_hasFocus = false; +}; + +#endif // CODE_FOCUS_HANDLER_H diff --git a/src/lib_gui/qt/element/code/QtCodeArea.cpp b/src/lib_gui/qt/element/code/QtCodeArea.cpp index 3fd42c00..efa23052 100644 --- a/src/lib_gui/qt/element/code/QtCodeArea.cpp +++ b/src/lib_gui/qt/element/code/QtCodeArea.cpp @@ -160,7 +160,7 @@ void QtCodeArea::lineNumberAreaPaintEvent(QPaintEvent* event) switch (annotation.locationType) { case LOCATION_LOCAL_SYMBOL: - if (annotation.isActive || annotation.isFocused) + if (annotation.isActive || annotation.isFocused || annotation.isCoFocused) { focus = true; } @@ -169,7 +169,7 @@ void QtCodeArea::lineNumberAreaPaintEvent(QPaintEvent* event) case LOCATION_ERROR: case LOCATION_FULLTEXT_SEARCH: case LOCATION_SCREEN_SEARCH: - if (annotation.isFocused || annotation.isActive) + if (annotation.isActive || annotation.isFocused || annotation.isCoFocused) { focus = true; } @@ -186,7 +186,7 @@ void QtCodeArea::lineNumberAreaPaintEvent(QPaintEvent* event) focus = true; break; } - else if (annotation.isFocused && utility::shareElement(activeSymbolIds, annotation.tokenIds)) + else if (annotation.isCoFocused && utility::shareElement(activeSymbolIds, annotation.tokenIds)) { active = true; break; @@ -197,7 +197,7 @@ void QtCodeArea::lineNumberAreaPaintEvent(QPaintEvent* event) { active = true; } - else if (annotation.isFocused) + else if (annotation.isFocused || annotation.isCoFocused) { focus = true; } @@ -226,6 +226,7 @@ void QtCodeArea::lineNumberAreaPaintEvent(QPaintEvent* event) QColor inactiveTextColor(scheme->getColor("code/snippet/line_number/inactive_text").c_str()); QColor activeMarkerColor(scheme->getColor("code/snippet/line_number/marker/active").c_str()); QColor focusedMarkerColor(scheme->getColor("code/snippet/line_number/marker/focus").c_str()); + QColor focusColor(QString::fromStdString(getFocusColor())); QPen p = painter.pen(); @@ -238,6 +239,13 @@ void QtCodeArea::lineNumberAreaPaintEvent(QPaintEvent* event) drawAreaBottom = height() - horizontalScrollBar()->height(); } + size_t focusedLineNumber = 0; + const CodeFocusHandler::Focus& currentFocus = m_navigator->getCurrentFocus(); + if (currentFocus.area == this && currentFocus.locationId) + { + focusedLineNumber = currentFocus.lineNumber; + } + while (block.isValid() && top <= drawAreaBottom) { if (block.isVisible() && bottom >= drawAreaTop) @@ -247,7 +255,11 @@ void QtCodeArea::lineNumberAreaPaintEvent(QPaintEvent* event) p.setColor(textColor); - if (focusedLineNumbers.find(number) != focusedLineNumbers.end()) + if (focusedLineNumber == number) + { + painter.fillRect(m_lineNumberArea->width() - 8, top, 3, height, focusColor); + } + else if (focusedLineNumbers.find(number) != focusedLineNumbers.end()) { painter.fillRect(m_lineNumberArea->width() - 8, top, 3, height, focusedMarkerColor); } @@ -394,7 +406,7 @@ size_t QtCodeArea::getActiveLocationCount() const for (const Annotation& annotation: m_annotations) { if (annotation.locationType == LocationType::LOCATION_TOKEN && - (annotation.isActive || annotation.isFocused)) + (annotation.isActive || annotation.isCoFocused)) { count++; } @@ -463,9 +475,6 @@ void QtCodeArea::findScreenMatches( matchAnnotation.locationId = ~(~Id(0) >> 2) + screenMatches->size() + 1; matchAnnotation.locationType = LOCATION_SCREEN_SEARCH; - matchAnnotation.isActive = false; - matchAnnotation.isFocused = false; - m_annotations.push_back(matchAnnotation); screenMatches->push_back(std::make_pair(this, matchAnnotation.locationId)); @@ -575,6 +584,146 @@ void QtCodeArea::ensureLocationIdVisible(Id locationId, int parentWidth, bool an } } +bool QtCodeArea::setFocus(Id locationId) +{ + for (const Annotation& annotation: m_annotations) + { + const LocationType& type = annotation.locationType; + if (annotation.locationId == locationId && + (type == LOCATION_TOKEN || type == LOCATION_QUALIFIER || + type == LOCATION_LOCAL_SYMBOL || type == LOCATION_UNSOLVED || type == LOCATION_ERROR)) + { + focusAnnotation(&annotation, true, false); + return true; + } + } + + return false; +} + +bool QtCodeArea::moveFocus(CodeFocusHandler::Direction direction, size_t lineNumber, Id locationId) +{ + switch (direction) + { + case CodeFocusHandler::Direction::UP: + return moveFocusToLine( + static_cast(lineNumber) - 1, static_cast(m_navigator->getTargetColumn()), true); + case CodeFocusHandler::Direction::DOWN: + return moveFocusToLine( + static_cast(lineNumber) + 1, static_cast(m_navigator->getTargetColumn()), false); + case CodeFocusHandler::Direction::LEFT: + return moveFocusInLine(lineNumber, locationId, false); + case CodeFocusHandler::Direction::RIGHT: + return moveFocusInLine(lineNumber, locationId, true); + }; + + return false; +} + +bool QtCodeArea::moveFocusToLine(int lineNumber, int targetColumn, bool up) +{ + while (true) + { + if (lineNumber < getStartLineNumber() || lineNumber > getEndLineNumber()) + { + break; + } + + std::vector annotations = getInteractiveAnnotationsForLineNumber( + lineNumber); + if (annotations.size()) + { + const Annotation* annotation = nullptr; + int dist = -1; + for (const Annotation* a: annotations) + { + if (dist < 0 || std::abs(a->startCol - targetColumn) < dist) + { + dist = std::abs(a->startCol - targetColumn); + annotation = a; + } + } + + if (annotation) + { + focusAnnotation(annotation, false, false); + } + return true; + } + + if (up) + { + lineNumber--; + } + else + { + lineNumber++; + } + } + + return false; +} + +bool QtCodeArea::moveFocusInLine(size_t lineNumber, Id locationId, bool forward) +{ + const Annotation* target = nullptr; + + for (const Annotation* annotation: getInteractiveAnnotationsForLineNumber(lineNumber)) + { + if (annotation->locationId == locationId) + { + if (forward) + { + target = annotation; + } + else + { + break; + } + } + else if (forward) + { + if (target) + { + target = annotation; + break; + } + } + else + { + target = annotation; + } + } + + if (target && target->locationId != locationId) + { + focusAnnotation(target, true, false); + return true; + } + + return false; +} + +void QtCodeArea::activateLocationId(Id locationId, bool fromMouse) +{ + const Annotation* annotation = getAnnotationForLocationId(locationId); + if (!annotation) + { + return; + } + + const std::set& localTokenIds = m_navigator->getActiveLocalTokenIds(); + if (annotation->locationType == LOCATION_LOCAL_SYMBOL && annotation->tokenIds.size() == 1 && + localTokenIds.find(*annotation->tokenIds.begin()) != localTokenIds.end()) + { + MessageActivateLocalSymbols({}).dispatch(); + } + else + { + activateAnnotationsOrErrors({annotation}, fromMouse); + } +} + void QtCodeArea::resizeEvent(QResizeEvent* e) { QPlainTextEdit::resizeEvent(e); @@ -618,9 +767,12 @@ void QtCodeArea::mousePressEvent(QMouseEvent* event) void QtCodeArea::mouseReleaseEvent(QMouseEvent* event) { - if (event->button() != Qt::LeftButton) + if (event->button() == Qt::MiddleButton || + (event->button() == Qt::LeftButton && event->modifiers() & Qt::ControlModifier && + event->modifiers() & Qt::ShiftModifier)) { - QtCodeField::mouseReleaseEvent(event); + checkOpenInTabActionEnabled(event->pos()); + openInTab(); return; } @@ -656,11 +808,11 @@ void QtCodeArea::mouseReleaseEvent(QMouseEvent* event) event->pos()); if (annotations.size()) { - activateAnnotationsOrErrors(annotations); + activateAnnotationsOrErrors(annotations, true); } else if (m_navigator->getActiveLocalTokenIds().size()) { - MessageActivateLocalSymbols(std::vector()).dispatch(); + MessageActivateLocalSymbols({}).dispatch(); } } } @@ -736,6 +888,11 @@ void QtCodeArea::mouseMoveEvent(QMouseEvent* event) QToolTip::hideText(); setHoveredAnnotations(annotations); + if (annotations.size()) + { + focusAnnotation(annotations.front(), true, true); + m_navigator->focusView(); + } } } @@ -860,7 +1017,8 @@ bool QtCodeArea::isSelectionPosition(const QPoint positionPoint) const return selectionStart != selectionEnd && selectionStart <= position && position <= selectionEnd; } -void QtCodeArea::activateAnnotationsOrErrors(const std::vector& annotations) +void QtCodeArea::activateAnnotationsOrErrors( + const std::vector& annotations, bool fromMouse) { if (m_navigator->hasErrors()) { @@ -881,7 +1039,19 @@ void QtCodeArea::activateAnnotationsOrErrors(const std::vectorstartLine); + m_navigator->setFocusedLocationId( + this, + annotation->startLine, + updateTargetColumn ? annotation->startCol : 0, + annotation->locationId, + utility::toVector(annotation->tokenIds), + fromMouse); } void QtCodeArea::annotateText() @@ -890,17 +1060,20 @@ void QtCodeArea::annotateText() const std::set& activeLocationIds = utility::concat( m_navigator->getCurrentActiveLocationIds(), m_navigator->getCurrentActiveLocalLocationIds()); - std::set focusedSymbolIds = m_navigator->getActiveTokenIds(); - utility::append(focusedSymbolIds, m_navigator->getActiveLocalTokenIds()); + std::set coFocusedSymbolIds = m_navigator->getActiveTokenIds(); + utility::append(coFocusedSymbolIds, m_navigator->getActiveLocalTokenIds()); for (Id currentActiveId: activeSymbolIds) { - focusedSymbolIds.erase(currentActiveId); + coFocusedSymbolIds.erase(currentActiveId); } - utility::append(focusedSymbolIds, m_navigator->getFocusedTokenIds()); + utility::append(coFocusedSymbolIds, m_navigator->getCoFocusedTokenIds()); bool needsUpdate = QtCodeField::annotateText( - activeSymbolIds, activeLocationIds, focusedSymbolIds); + activeSymbolIds, + activeLocationIds, + coFocusedSymbolIds, + m_navigator->getCurrentFocus().locationId); if (needsUpdate) { m_lineNumberArea->update(); diff --git a/src/lib_gui/qt/element/code/QtCodeArea.h b/src/lib_gui/qt/element/code/QtCodeArea.h index 3840d2ab..b39de503 100644 --- a/src/lib_gui/qt/element/code/QtCodeArea.h +++ b/src/lib_gui/qt/element/code/QtCodeArea.h @@ -4,6 +4,7 @@ #include #include +#include "CodeFocusHandler.h" #include "QtCodeField.h" #include "QtScrollSpeedChangeListener.h" @@ -86,6 +87,12 @@ public: void ensureLocationIdVisible(Id locationId, int parentWidth, bool animated); + bool setFocus(Id locationId); + bool moveFocus(CodeFocusHandler::Direction direction, size_t lineNumber, Id locationId); + bool moveFocusToLine(int lineNumber, int targetColumn, bool up); + bool moveFocusInLine(size_t lineNumber, Id locationId, bool forward); + void activateLocationId(Id locationId, bool fromMouse); + protected: virtual void resizeEvent(QResizeEvent* event) override; virtual void mouseReleaseEvent(QMouseEvent* event) override; @@ -100,7 +107,7 @@ protected: private slots: void updateLineNumberAreaWidth(int newBlockCount = 0); - void updateLineNumberArea(QRect , int); + void updateLineNumberArea(QRect, int); void setIDECursorPosition(); void setCopyAvailable(bool yes); @@ -110,7 +117,8 @@ private: void dragSelectedText(); bool isSelectionPosition(QPoint positionPoint) const; - void activateAnnotationsOrErrors(const std::vector& annotations); + void activateAnnotationsOrErrors(const std::vector& annotations, bool fromMouse); + void focusAnnotation(const Annotation* annotation, bool updateTargetColumn, bool fromMouse); void annotateText(); diff --git a/src/lib_gui/qt/element/code/QtCodeField.cpp b/src/lib_gui/qt/element/code/QtCodeField.cpp index 8308d786..bf3538b3 100644 --- a/src/lib_gui/qt/element/code/QtCodeField.cpp +++ b/src/lib_gui/qt/element/code/QtCodeField.cpp @@ -4,6 +4,7 @@ #include #include #include +#include #include "ApplicationSettings.h" #include "ColorScheme.h" @@ -21,10 +22,12 @@ #include "utility.h" std::vector QtCodeField::s_annotationColors; +std::string QtCodeField::s_focusColor; void QtCodeField::clearAnnotationColors() { s_annotationColors.clear(); + s_focusColor.clear(); } QtCodeField::QtCodeField( @@ -96,7 +99,11 @@ QtCodeField::QtCodeField( setFont(font); setTabStopWidth(appSettings->getCodeTabWidth() * fontMetrics().width('9')); - m_openInTabAction = new QAction(QStringLiteral("Open in New Tab"), this); + m_openInTabAction = new QAction( + QStringLiteral("Open in New Tab (Ctrl + Shift + Left Click)"), this); +#if defined(Q_OS_MAC) + m_openInTabAction->setText(QStringLiteral("Open in New Tab (Cmd + Shift + Left Click)")); +#endif m_openInTabAction->setStatusTip(QStringLiteral("Opens the node in a new tab")); m_openInTabAction->setToolTip(QStringLiteral("Opens the node in a new tab")); m_openInTabAction->setEnabled(false); @@ -157,9 +164,14 @@ std::shared_ptr QtCodeField::getSourceLocationFile() const return m_locationFile; } +FilePath QtCodeField::getFilePath() const +{ + return m_locationFile ? m_locationFile->getFilePath() : FilePath(); +} + void QtCodeField::annotateText() { - annotateText(std::set(), std::set(), std::set()); + annotateText(std::set(), std::set(), std::set(), 0); } void QtCodeField::paintEvent(QPaintEvent* event) @@ -199,6 +211,7 @@ void QtCodeField::paintEvent(QPaintEvent* event) lastVisibleLine += static_cast(m_startLineNumber); int borderRadius = 3; + QColor focusColor(QString::fromStdString(getFocusColor())); for (const Annotation& annotation: m_annotations) { @@ -217,7 +230,7 @@ void QtCodeField::paintEvent(QPaintEvent* event) setTextColorForAnnotation(annotation, QColor(color.text.c_str())); } - if (color.border == "transparent" && color.fill == "transparent") + if (!annotation.isFocused && color.border == "transparent" && color.fill == "transparent") { continue; } @@ -227,6 +240,11 @@ void QtCodeField::paintEvent(QPaintEvent* event) { pen.setStyle(Qt::DashLine); } + if (annotation.isFocused) + { + pen.setColor(focusColor); + pen.setWidth(2); + } painter.setPen(pen); painter.setBrush(QBrush(color.fill.c_str())); @@ -306,7 +324,22 @@ void QtCodeField::mouseReleaseEvent(QMouseEvent* event) return; } - activateAnnotations(annotations); + activateAnnotations(annotations, true, 0); +} + +void QtCodeField::keyPressEvent(QKeyEvent* event) +{ + switch (event->key()) + { + case Qt::Key_Up: + case Qt::Key_Down: + case Qt::Key_Left: + case Qt::Key_Right: + QWidget::keyPressEvent(event); + return; + } + + QPlainTextEdit::keyPressEvent(event); } void QtCodeField::contextMenuEvent(QContextMenuEvent* event) @@ -322,38 +355,42 @@ void QtCodeField::contextMenuEvent(QContextMenuEvent* event) void QtCodeField::focusTokenIds(const std::vector& focusedTokenIds) { annotateText( - std::set(), std::set(), std::set(focusedTokenIds.begin(), focusedTokenIds.end())); + std::set(), std::set(), std::set(focusedTokenIds.begin(), focusedTokenIds.end()), 0); } void QtCodeField::defocusTokenIds(const std::vector& activeTokenIds) { - annotateText(std::set(), std::set(), std::set()); + annotateText(std::set(), std::set(), std::set(), 0); } bool QtCodeField::annotateText( const std::set& activeSymbolIds, const std::set& activeLocationIds, - const std::set& focusedSymbolIds) + const std::set& coFocusedSymbolIds, + Id focusedLocationId) { for (size_t i = 0; i < m_annotations.size(); i++) { Annotation& annotation = m_annotations[i]; bool wasActive = annotation.isActive; bool wasFocused = annotation.isFocused; + bool wasCoFocused = annotation.isCoFocused; annotation.isActive = (utility::shareElement(activeSymbolIds, annotation.tokenIds) || activeLocationIds.find(annotation.locationId) != activeLocationIds.end()); - annotation.isFocused = utility::shareElement(focusedSymbolIds, annotation.tokenIds); + annotation.isFocused = focusedLocationId && (annotation.locationId == focusedLocationId); + + annotation.isCoFocused = utility::shareElement(coFocusedSymbolIds, annotation.tokenIds); if (annotation.locationType == LOCATION_QUALIFIER) { // never show qualifier locations active annotation.isActive = false; - // only show qualifiers focused when mouse over - if (annotation.isFocused) + // only show qualifiers cofocused when mouse over + if (annotation.isCoFocused) { bool isHovered = false; for (const Annotation* a: m_hoveredAnnotations) @@ -363,11 +400,12 @@ bool QtCodeField::annotateText( isHovered = true; } } - annotation.isFocused = isHovered; + annotation.isCoFocused = isHovered; } } - if (wasFocused != annotation.isFocused || wasActive != annotation.isActive) + if (wasActive != annotation.isActive || wasFocused != annotation.isFocused || + wasCoFocused != annotation.isCoFocused) { m_linesToRehighlight.push_back(static_cast(annotation.startLine - m_startLineNumber)); } @@ -450,14 +488,12 @@ void QtCodeField::createAnnotations(std::shared_ptr location annotation.locationId = location->getLocationId(); annotation.locationType = location->getType(); - annotation.isActive = false; - annotation.isFocused = false; - m_annotations.push_back(annotation); }); } -void QtCodeField::activateAnnotations(const std::vector& annotations) +void QtCodeField::activateAnnotations( + const std::vector& annotations, bool fromMouse, int mouseOffsetX) { std::vector locationIds; std::set tokenIds; @@ -498,6 +534,27 @@ void QtCodeField::activateAnnotations(const std::vector& anno if (tokenIds.size() > 1 || localSymbolIds.size() > 1 || (tokenIds.size() && localSymbolIds.size())) { + // FIXME: Tooltip lists are positioned at the mouse cursor. This workaround sets the mouse + // cursor to the location of the activated annotation if activated via keyboard. + if (!fromMouse) + { + std::vector rects = getCursorRectsForAnnotation(*annotations.front()); + if (rects.size()) + { + m_hoveredAnnotations = annotations; + + QPoint pos = mapToGlobal(rects.front().translated(mouseOffsetX, 0).center()); + if (window() && window()->windowHandle() && window()->windowHandle()->screen()) + { + QCursor::setPos(window()->windowHandle()->screen(), pos); + } + else + { + QCursor::setPos(pos); + } + } + } + MessageTooltipShow(locationIds, utility::toVector(localSymbolIds), TOOLTIP_ORIGIN_CODE) .dispatch(); } @@ -683,7 +740,7 @@ const QtCodeField::AnnotationColor& QtCodeField::getAnnotationColorForAnnotation { i += 2; } - else if (annotation.isFocused) + else if (annotation.isCoFocused) { i += 1; } @@ -698,6 +755,49 @@ void QtCodeField::setTextColorForAnnotation(const Annotation& annotation, QColor m_highlighter->applyFormat(annotation.start, annotation.end, format); } +const std::string& QtCodeField::getFocusColor() +{ + if (s_focusColor.empty()) + { + s_focusColor = ColorScheme::getInstance()->getColor("window/focus"); + } + + return s_focusColor; +} + +const QtCodeField::Annotation* QtCodeField::getAnnotationForLocationId(Id locationId) const +{ + for (const Annotation& annotation: m_annotations) + { + if (annotation.locationId == locationId) + { + return &annotation; + } + } + + return nullptr; +} + +std::vector QtCodeField::getInteractiveAnnotationsForLineNumber( + size_t lineNumber) const +{ + std::vector annotations; + + for (const Annotation& annotation: m_annotations) + { + const LocationType& type = annotation.locationType; + if ((type == LOCATION_TOKEN || type == LOCATION_QUALIFIER || + type == LOCATION_LOCAL_SYMBOL || type == LOCATION_UNSOLVED || type == LOCATION_ERROR) && + static_cast(lineNumber) >= annotation.startLine && + static_cast(lineNumber) <= annotation.endLine) + { + annotations.push_back(&annotation); + } + } + + return annotations; +} + std::vector QtCodeField::getInteractiveAnnotationsForPosition( QPoint position) const { diff --git a/src/lib_gui/qt/element/code/QtCodeField.h b/src/lib_gui/qt/element/code/QtCodeField.h index 4346fbbd..5f250603 100644 --- a/src/lib_gui/qt/element/code/QtCodeField.h +++ b/src/lib_gui/qt/element/code/QtCodeField.h @@ -6,6 +6,7 @@ #include +#include "FilePath.h" #include "LocationType.h" #include "types.h" @@ -29,7 +30,7 @@ public: ~QtCodeField(); - virtual QSize sizeHint() const override; + QSize sizeHint() const override; size_t getStartLineNumber() const; size_t getEndLineNumber() const; @@ -39,40 +40,44 @@ public: std::string getCode() const; std::shared_ptr getSourceLocationFile() const; + FilePath getFilePath() const; void annotateText(); protected: - virtual void paintEvent(QPaintEvent* event) override; - virtual void enterEvent(QEvent* event) override; - virtual void leaveEvent(QEvent* event) override; + void paintEvent(QPaintEvent* event) override; + void enterEvent(QEvent* event) override; + void leaveEvent(QEvent* event) override; - virtual void mouseMoveEvent(QMouseEvent* event) override; - virtual void mouseReleaseEvent(QMouseEvent* event) override; + void mouseMoveEvent(QMouseEvent* event) override; + void mouseReleaseEvent(QMouseEvent* event) override; - virtual void contextMenuEvent(QContextMenuEvent* event) override; + void keyPressEvent(QKeyEvent* event) override; + + void contextMenuEvent(QContextMenuEvent* event) override; virtual void focusTokenIds(const std::vector& tokenIds); virtual void defocusTokenIds(const std::vector& tokenIds); struct Annotation { - int startLine; - int endLine; + int startLine = 0; + int endLine = 0; - int startCol; - int endCol; + int startCol = 0; + int endCol = 0; - int start; - int end; + int start = 0; + int end = 0; std::set tokenIds; - Id locationId; + Id locationId = 0; - LocationType locationType; + LocationType locationType = LOCATION_TOKEN; - bool isActive; - bool isFocused; + bool isActive = false; + bool isFocused = false; + bool isCoFocused = false; }; struct AnnotationColor @@ -85,10 +90,11 @@ protected: bool annotateText( const std::set& activeSymbolIds, const std::set& activeLocationIds, - const std::set& focusedSymbolIds); + const std::set& focusedSymbolIds, + Id focusedLocationId); void createAnnotations(std::shared_ptr locationFile); - void activateAnnotations(const std::vector& annotations); + void activateAnnotations(const std::vector& annotations, bool fromMouse, int mouseOffsetX); int toTextEditPosition(int lineNumber, int columnNumber) const; std::pair toLineColumn(int textEditPosition) const; @@ -102,6 +108,10 @@ protected: const AnnotationColor& getAnnotationColorForAnnotation(const Annotation& annotation); void setTextColorForAnnotation(const Annotation& annotation, QColor color) const; + const std::string& getFocusColor(); + + const Annotation* getAnnotationForLocationId(Id locationId) const; + std::vector getInteractiveAnnotationsForLineNumber(size_t lineNumber) const; std::vector getInteractiveAnnotationsForPosition(QPoint position) const; std::vector getInteractiveTokenIdsForPosition(QPoint position) const; @@ -113,11 +123,12 @@ protected: QAction* m_openInTabAction; -private slots: +protected slots: void openInTab(); private: static std::vector s_annotationColors; + static std::string s_focusColor; void createLineLengthCache(); void createMultibyteCharacterLocationCache(const QString& code); diff --git a/src/lib_gui/qt/element/code/QtCodeFile.cpp b/src/lib_gui/qt/element/code/QtCodeFile.cpp index 9addddbb..5cbe68f7 100644 --- a/src/lib_gui/qt/element/code/QtCodeFile.cpp +++ b/src/lib_gui/qt/element/code/QtCodeFile.cpp @@ -33,6 +33,10 @@ QtCodeFile::QtCodeFile(const FilePath& filePath, QtCodeNavigator* navigator, boo connect(m_titleBar, &QtCodeFileTitleBar::minimize, this, &QtCodeFile::clickedMinimizeButton); connect(m_titleBar, &QtCodeFileTitleBar::snippet, this, &QtCodeFile::clickedSnippetButton); connect(m_titleBar, &QtCodeFileTitleBar::maximize, this, &QtCodeFile::clickedMaximizeButton); + connect(m_titleBar, &QtHoverButton::hoveredIn, [this](){ + m_navigator->setFocusedFile(this); + m_navigator->setFocus(); + }); m_snippetLayout = new QVBoxLayout(); m_snippetLayout->setContentsMargins(0, 0, 0, 0); @@ -189,6 +193,8 @@ void QtCodeFile::updateContent() { snippet->updateContent(); } + + m_titleBar->setIsFocused(m_navigator->getCurrentFocus().file == this); } void QtCodeFile::setWholeFile(bool isWholeFile, int refCount) @@ -208,6 +214,23 @@ void QtCodeFile::setIsIndexed(bool isIndexed) m_titleBar->setIsIndexed(isIndexed); } +bool QtCodeFile::isCollapsed() const +{ + return m_titleBar->isCollapsed(); +} + +void QtCodeFile::toggleCollapsed() +{ + if (isCollapsed()) + { + clickedSnippetButton(); + } + else + { + clickedMinimizeButton(); + } +} + void QtCodeFile::setMinimized() { for (QtCodeSnippet* snippet: m_snippets) @@ -288,6 +311,99 @@ void QtCodeFile::findScreenMatches( } } +bool QtCodeFile::hasFocus(const CodeFocusHandler::Focus& focus) const +{ + if (focus.file == this) + { + return true; + } + + for (QtCodeSnippet* snippet: m_snippets) + { + if (snippet->hasFocus(focus)) + { + return true; + } + } + + return false; +} + +bool QtCodeFile::setFocus(Id locationId) +{ + for (QtCodeSnippet* snippet: m_snippets) + { + if (snippet->setFocus(locationId)) + { + return true; + } + } + + return false; +} + +bool QtCodeFile::moveFocus(const CodeFocusHandler::Focus& focus, CodeFocusHandler::Direction direction) +{ + if (direction == CodeFocusHandler::Direction::DOWN && focus.file == this && !isCollapsed() && + m_snippets.size()) + { + m_snippets[0]->focusTop(); + return true; + } + + for (size_t i = 0; i < m_snippets.size(); i++) + { + QtCodeSnippet* snippet = m_snippets[i]; + + if (snippet->moveFocus(focus, direction)) + { + return true; + } + else if (snippet->hasFocus(focus)) + { + if (direction == CodeFocusHandler::Direction::UP) + { + if (i > 0) + { + m_snippets[i - 1]->focusBottom(); + } + else + { + m_navigator->setFocusedFile(this); + } + return true; + } + else if ( + direction == CodeFocusHandler::Direction::DOWN && !isCollapsed() && + i < m_snippets.size() - 1) + { + m_snippets[i + 1]->focusTop(); + return true; + } + break; + } + } + + return false; +} + +void QtCodeFile::focusTop() +{ + m_navigator->setFocusedFile(this); +} + +void QtCodeFile::focusBottom() +{ + if (!isCollapsed() && m_snippets.size()) + { + m_snippets.back()->focusBottom(); + } + else + { + m_navigator->setFocusedFile(this); + } +} + void QtCodeFile::clickedMinimizeButton() { MessageChangeFileView msg( @@ -334,7 +450,7 @@ void QtCodeFile::clickedMaximizeButton() m_navigator->setMode(QtCodeNavigator::MODE_SINGLE); CodeScrollParams scrollParams = locationId - ? CodeScrollParams::toReference(m_filePath, locationId, CodeScrollParams::Target::CENTER) + ? CodeScrollParams::toReference(m_filePath, locationId, 0, CodeScrollParams::Target::CENTER) : CodeScrollParams::toLine(m_filePath, lineNumber, CodeScrollParams::Target::CENTER); MessageChangeFileView msg( diff --git a/src/lib_gui/qt/element/code/QtCodeFile.h b/src/lib_gui/qt/element/code/QtCodeFile.h index 6e22b649..5d8438af 100644 --- a/src/lib_gui/qt/element/code/QtCodeFile.h +++ b/src/lib_gui/qt/element/code/QtCodeFile.h @@ -10,6 +10,7 @@ #include "FilePath.h" +#include "CodeFocusHandler.h" #include "CodeSnippetParams.h" class QtCodeArea; @@ -50,6 +51,9 @@ public: void setIsComplete(bool isComplete); void setIsIndexed(bool isIndexed); + bool isCollapsed() const; + void toggleCollapsed(); + void setMinimized(); void setSnippets(); @@ -61,6 +65,12 @@ public: void findScreenMatches( const std::wstring& query, std::vector>* screenMatches); + bool hasFocus(const CodeFocusHandler::Focus& focus) const; + bool setFocus(Id locationId); + bool moveFocus(const CodeFocusHandler::Focus& focus, CodeFocusHandler::Direction direction); + void focusTop(); + void focusBottom(); + public slots: void clickedMinimizeButton(); void clickedSnippetButton(); diff --git a/src/lib_gui/qt/element/code/QtCodeFileList.cpp b/src/lib_gui/qt/element/code/QtCodeFileList.cpp index 6c9b158f..0db1b542 100644 --- a/src/lib_gui/qt/element/code/QtCodeFileList.cpp +++ b/src/lib_gui/qt/element/code/QtCodeFileList.cpp @@ -17,13 +17,28 @@ #include "SourceLocationFile.h" #include "utilityQt.h" +void QtCodeFileListScrollArea::keyPressEvent(QKeyEvent* event) +{ + switch (event->key()) + { + case Qt::Key_Up: + case Qt::Key_Down: + case Qt::Key_Left: + case Qt::Key_Right: + QWidget::keyPressEvent(event); + return; + } + + QScrollArea::keyPressEvent(event); +} + QtCodeFileList::QtCodeFileList(QtCodeNavigator* navigator) : QFrame() , m_navigator(navigator) , m_mirroredTitleBar(nullptr) , m_mirroredSnippetScrollBar(nullptr) { - m_scrollArea = new QScrollArea(); + m_scrollArea = new QtCodeFileListScrollArea(); m_scrollArea->setObjectName(QStringLiteral("code_container")); m_scrollArea->setWidgetResizable(true); @@ -160,12 +175,24 @@ void QtCodeFileList::addFile(const CodeFileParams& params) if (!same) { + Id focusedLocationId = 0; + const CodeFocusHandler::Focus& currentFocus = m_navigator->getCurrentFocus(); + if (currentFocus.area && file->getFilePath() == currentFocus.area->getSourceLocationFile()->getFilePath()) + { + focusedLocationId = currentFocus.locationId; + } + file->clearSnippets(); for (const CodeSnippetParams& snippetParams: params.snippetParams) { file->addCodeSnippet(snippetParams); } + + if (focusedLocationId) + { + file->setFocus(focusedLocationId); + } } file->setSnippets(); @@ -204,8 +231,10 @@ void QtCodeFileList::scrollTo( const FilePath& filePath, size_t lineNumber, Id locationId, + Id scopeLocationId, bool animated, - CodeScrollParams::Target target) + CodeScrollParams::Target target, + bool focusTarget) { QtCodeFile* file = getFile(filePath); if (!file) @@ -215,9 +244,10 @@ void QtCodeFileList::scrollTo( QtCodeSnippet* snippet = nullptr; - if (locationId) + Id targetLocationId = scopeLocationId ? scopeLocationId : locationId; + if (targetLocationId) { - snippet = file->getSnippetForLocationId(locationId); + snippet = file->getSnippetForLocationId(targetLocationId); } else if (lineNumber) { @@ -226,6 +256,7 @@ void QtCodeFileList::scrollTo( else { snippet = file->getSnippetForLine(1); + focusTarget = false; } if (!snippet || !snippet->isVisible()) @@ -237,9 +268,10 @@ void QtCodeFileList::scrollTo( size_t endLineNumber = 0; if (!lineNumber) { - if (locationId) + if (targetLocationId) { - std::pair lineNumbers = snippet->getLineNumbersForLocationId(locationId); + std::pair lineNumbers = snippet->getLineNumbersForLocationId( + targetLocationId); lineNumber = lineNumbers.first; @@ -251,6 +283,7 @@ void QtCodeFileList::scrollTo( else { lineNumber = 1; + focusTarget = false; } } @@ -262,7 +295,12 @@ void QtCodeFileList::scrollTo( ensureWidgetVisibleAnimated(m_filesArea, snippet, lineRect, animated, target); - snippet->ensureLocationIdVisible(locationId, animated); + snippet->ensureLocationIdVisible(targetLocationId, animated); + + if (focusTarget) + { + m_navigator->setFocusedLocationId(snippet->getArea(), lineNumber, 0, locationId, {}, false); + } } void QtCodeFileList::onWindowFocus() @@ -287,6 +325,50 @@ void QtCodeFileList::findScreenMatches( } } +void QtCodeFileList::setFocus(Id locationId) +{ + for (QtCodeFile* file: m_files) + { + if (file->setFocus(locationId)) + { + return; + } + } +} + +void QtCodeFileList::setFocusOnTop() +{ + if (m_files.size()) + { + m_files[0]->focusTop(); + } +} + +void QtCodeFileList::moveFocus(const CodeFocusHandler::Focus& focus, CodeFocusHandler::Direction direction) +{ + for (size_t i = 0; i < m_files.size(); i++) + { + QtCodeFile* file = m_files[i]; + + if (file->moveFocus(focus, direction)) + { + return; + } + else if (file->hasFocus(focus)) + { + if (direction == CodeFocusHandler::Direction::UP && i > 0) + { + m_files[i - 1]->focusBottom(); + } + else if (direction == CodeFocusHandler::Direction::DOWN && i < m_files.size() - 1) + { + m_files[i + 1]->focusTop(); + } + return; + } + } +} + void QtCodeFileList::maximizeFirstFile() { if (m_files.size()) @@ -309,16 +391,17 @@ std::pair QtCodeFileList::getFirstFileWithActiveLocationId() co return std::make_pair(nullptr, 0); } -std::pair QtCodeFileList::getFirstSnippetWithActiveLocationId(Id tokenId) const +std::pair QtCodeFileList::getFirstSnippetAndActiveLocationId() const { std::pair result(nullptr, 0); - for (QtCodeFile* file: m_files) + if (m_files.size()) { - result = file->getFirstSnippetWithActiveLocationId(tokenId); - if (result.first != nullptr) + std::vector snippets = m_files[0]->getVisibleSnippets(); + if (snippets.size()) { - break; + result.first = snippets[0]; + result.second = snippets[0]->getFirstActiveLocationId(0); } } @@ -334,6 +417,11 @@ void QtCodeFileList::resizeEvent(QResizeEvent* event) void QtCodeFileList::updateSnippetTitleAndScrollBarSlot() { updateSnippetTitleAndScrollBar(0); + + if (m_firstSnippetTitleBar && m_firstSnippetFile) + { + m_firstSnippetTitleBar->setIsFocused(m_navigator->getCurrentFocus().file == m_firstSnippetFile); + } } void QtCodeFileList::updateSnippetTitleAndScrollBar(int value) @@ -402,7 +490,7 @@ void QtCodeFileList::scrollLastSnippetScrollBar(int value) } } -void QtCodeFileList::updateFirstSnippetTitleBar(const QtCodeFile* file, int fileTitleBarOffset) +void QtCodeFileList::updateFirstSnippetTitleBar(QtCodeFile* file, int fileTitleBarOffset) { const QtCodeFileTitleBar* mirroredTitleBar = file ? file->getTitleBar() : nullptr; if (m_mirroredTitleBar != mirroredTitleBar) @@ -428,6 +516,10 @@ void QtCodeFileList::updateFirstSnippetTitleBar(const QtCodeFile* file, int file &QtCodeFileTitleBar::maximize, file, &QtCodeFile::clickedMaximizeButton); + connect(m_firstSnippetTitleBar, &QtHoverButton::hoveredIn, [this, file](){ + m_navigator->setFocusedFile(file); + m_navigator->setFocus(); + }); m_firstSnippetTitleBar->setGeometry( file->pos().x() + mirroredTitleBar->pos().x(), @@ -435,6 +527,8 @@ void QtCodeFileList::updateFirstSnippetTitleBar(const QtCodeFile* file, int file mirroredTitleBar->width(), mirroredTitleBar->height()); m_firstSnippetTitleBar->show(); + + m_firstSnippetFile = file; } else { @@ -442,6 +536,8 @@ void QtCodeFileList::updateFirstSnippetTitleBar(const QtCodeFile* file, int file // issue when changing color scheme m_firstSnippetTitleBar->getTitleButton()->setFilePath(FilePath()); m_firstSnippetTitleBar->hide(); + + m_firstSnippetFile = nullptr; } } diff --git a/src/lib_gui/qt/element/code/QtCodeFileList.h b/src/lib_gui/qt/element/code/QtCodeFileList.h index 63973d6b..32ad69c9 100644 --- a/src/lib_gui/qt/element/code/QtCodeFileList.h +++ b/src/lib_gui/qt/element/code/QtCodeFileList.h @@ -15,6 +15,12 @@ class QtCodeFileTitleBar; class QtCodeNavigator; class QtCodeSnippet; +class QtCodeFileListScrollArea: public QScrollArea +{ +protected: + void keyPressEvent(QKeyEvent* event) override; +}; + class QtCodeFileList : public QFrame , public QtCodeNavigateable @@ -32,7 +38,7 @@ public: void addFile(const CodeFileParams& params); - // QtCodeNaviatebale implementation + // QtCodeNavigatebale implementation QScrollArea* getScrollArea() override; void updateSourceLocations(const CodeSnippetParams& params) override; @@ -42,18 +48,24 @@ public: const FilePath& filePath, size_t lineNumber, Id locationId, + Id scopeLocationId, bool animated, - CodeScrollParams::Target target) override; + CodeScrollParams::Target target, + bool focusTarget) override; void onWindowFocus() override; void findScreenMatches( const std::wstring& query, std::vector>* screenMatches) override; + void setFocus(Id locationId) override; + void setFocusOnTop() override; + void moveFocus(const CodeFocusHandler::Focus& focus, CodeFocusHandler::Direction direction) override; + void maximizeFirstFile(); std::pair getFirstFileWithActiveLocationId() const; - std::pair getFirstSnippetWithActiveLocationId(Id tokenId) const; + std::pair getFirstSnippetAndActiveLocationId() const; protected: void resizeEvent(QResizeEvent* event) override; @@ -66,16 +78,17 @@ private slots: void scrollLastSnippetScrollBar(int value); private: - void updateFirstSnippetTitleBar(const QtCodeFile* file, int fileTitleBarOffset = 0); + void updateFirstSnippetTitleBar(QtCodeFile* file, int fileTitleBarOffset = 0); void updateLastSnippetScrollBar(QScrollBar* mirroredScrollBar); QtCodeNavigator* m_navigator; - QScrollArea* m_scrollArea; + QtCodeFileListScrollArea* m_scrollArea; QFrame* m_filesArea; std::vector m_files; QtCodeFileTitleBar* m_firstSnippetTitleBar; + const QtCodeFile* m_firstSnippetFile = nullptr; const QtCodeFileTitleBar* m_mirroredTitleBar; QScrollBar* m_lastSnippetScrollBar; diff --git a/src/lib_gui/qt/element/code/QtCodeFileSingle.cpp b/src/lib_gui/qt/element/code/QtCodeFileSingle.cpp index 2fa3c36b..27d6cfa6 100644 --- a/src/lib_gui/qt/element/code/QtCodeFileSingle.cpp +++ b/src/lib_gui/qt/element/code/QtCodeFileSingle.cpp @@ -169,8 +169,10 @@ void QtCodeFileSingle::scrollTo( const FilePath& filePath, size_t lineNumber, Id locationId, + Id scopeLocationId, bool animated, - CodeScrollParams::Target target) + CodeScrollParams::Target target, + bool focusTarget) { if (m_currentFilePath != filePath) { @@ -184,12 +186,15 @@ void QtCodeFileSingle::scrollTo( animated = false; } + Id targetLocationId = scopeLocationId ? scopeLocationId : locationId; + size_t endLineNumber = 0; if (!lineNumber) { - if (locationId) + if (targetLocationId) { - std::pair lineNumbers = m_area->getLineNumbersForLocationId(locationId); + std::pair lineNumbers = m_area->getLineNumbersForLocationId( + targetLocationId); lineNumber = lineNumbers.first; @@ -208,7 +213,12 @@ void QtCodeFileSingle::scrollTo( double percentB = endLineNumber ? double(endLineNumber - 1) / m_area->getEndLineNumber() : 0.0f; ensurePercentVisibleAnimated(percentA, percentB, animated, target); - m_area->ensureLocationIdVisible(locationId, width(), animated); + m_area->ensureLocationIdVisible(targetLocationId, width(), animated); + + if (focusTarget && locationId) + { + m_navigator->setFocusedLocationId(m_area, lineNumber, 0, locationId, {}, false); + } } void QtCodeFileSingle::onWindowFocus() @@ -225,6 +235,31 @@ void QtCodeFileSingle::findScreenMatches( } } +void QtCodeFileSingle::setFocus(Id locationId) +{ + if (m_area) + { + m_area->setFocus(locationId); + } +} + +void QtCodeFileSingle::setFocusOnTop() +{ + if (m_area) + { + m_area->moveFocusToLine(static_cast(m_area->getStartLineNumber()), 0, false); + } +} + +void QtCodeFileSingle::moveFocus( + const CodeFocusHandler::Focus& focus, CodeFocusHandler::Direction direction) +{ + if (m_area == focus.area) + { + focus.area->moveFocus(direction, focus.lineNumber, focus.locationId); + } +} + const FilePath& QtCodeFileSingle::getCurrentFilePath() const { return m_currentFilePath; diff --git a/src/lib_gui/qt/element/code/QtCodeFileSingle.h b/src/lib_gui/qt/element/code/QtCodeFileSingle.h index ae468162..f6d89c09 100644 --- a/src/lib_gui/qt/element/code/QtCodeFileSingle.h +++ b/src/lib_gui/qt/element/code/QtCodeFileSingle.h @@ -42,14 +42,20 @@ public: const FilePath& filePath, size_t lineNumber, Id locationId, + Id scopeLocationId, bool animated, - CodeScrollParams::Target target) override; + CodeScrollParams::Target target, + bool focusTarget) override; void onWindowFocus() override; void findScreenMatches( const std::wstring& query, std::vector>* screenMatches) override; + void setFocus(Id locationId) override; + void setFocusOnTop() override; + void moveFocus(const CodeFocusHandler::Focus& focus, CodeFocusHandler::Direction direction) override; + const FilePath& getCurrentFilePath() const; bool hasFileCached(const FilePath& filePath) const; diff --git a/src/lib_gui/qt/element/code/QtCodeFileTitleBar.cpp b/src/lib_gui/qt/element/code/QtCodeFileTitleBar.cpp index a2703ef3..5f17489c 100644 --- a/src/lib_gui/qt/element/code/QtCodeFileTitleBar.cpp +++ b/src/lib_gui/qt/element/code/QtCodeFileTitleBar.cpp @@ -2,6 +2,7 @@ #include #include +#include #include #include "Application.h" @@ -189,6 +190,17 @@ void QtCodeFileTitleBar::updateRefCount(int refCount, bool hasErrors, size_t fat } } +bool QtCodeFileTitleBar::isCollapsed() const +{ + return m_expandButton->isVisible(); +} + +void QtCodeFileTitleBar::setIsFocused(bool focused) +{ + setProperty("focused", focused); + style()->polish(this); +} + void QtCodeFileTitleBar::setMinimized() { m_expandButton->setVisible(true); diff --git a/src/lib_gui/qt/element/code/QtCodeFileTitleBar.h b/src/lib_gui/qt/element/code/QtCodeFileTitleBar.h index 56bffdd7..7196315f 100644 --- a/src/lib_gui/qt/element/code/QtCodeFileTitleBar.h +++ b/src/lib_gui/qt/element/code/QtCodeFileTitleBar.h @@ -27,6 +27,10 @@ public: void setIsIndexed(bool isIndexed); void updateRefCount(int refCount, bool hasErrors, size_t fatalErrorCount); + bool isCollapsed() const; + + void setIsFocused(bool focused); + void setMinimized(); void setSnippets(); void setMaximized(); diff --git a/src/lib_gui/qt/element/code/QtCodeNavigateable.cpp b/src/lib_gui/qt/element/code/QtCodeNavigateable.cpp index 5c6d92d0..b52d898b 100644 --- a/src/lib_gui/qt/element/code/QtCodeNavigateable.cpp +++ b/src/lib_gui/qt/element/code/QtCodeNavigateable.cpp @@ -46,13 +46,13 @@ void QtCodeNavigateable::ensureWidgetVisibleAnimated( switch (target) { case CodeScrollParams::Target::VISIBLE: - if (focusRect.top() > visibleRect.top() && focusRect.bottom() < visibleRect.bottom()) + if (focusRect.top() > visibleRect.top() + 100 && focusRect.bottom() < visibleRect.bottom()) { return; } - else if (focusRect.top() < visibleRect.top()) + else if (focusRect.top() < visibleRect.top() + 100) { - value = focusRect.top() - visibleRect.top() - 50; + value = focusRect.top() - visibleRect.top() - 150; } else { @@ -128,10 +128,27 @@ void QtCodeNavigateable::ensurePercentVisibleAnimated( switch (target) { case CodeScrollParams::Target::VISIBLE: - if (scrollY > visibleY && scrollY + rectHeight < visibleY + scrollableHeight) { - return; + int visibleTop = visibleY + 50; + int visibleBottom = visibleY + visibleHeight - 50; + + int scrollTop = scrollY; + int scrollBottom = scrollY + rectHeight; + + if (scrollTop < visibleTop) + { + scrollY = scrollTop - 50; + } + else if (scrollBottom > visibleBottom) + { + scrollY = visibleTop + scrollBottom - visibleBottom; + } + else + { + return; + } } + break; case CodeScrollParams::Target::CENTER: if (rectHeight < visibleHeight / 2) @@ -149,10 +166,10 @@ void QtCodeNavigateable::ensurePercentVisibleAnimated( break; } - const int value = static_cast(scrollY * scrollFactor); - const int diff = value - scrollBar->value(); + const int diff = visibleY - scrollY; if (diff > 5 || diff < -5) { + const int value = static_cast(scrollY * scrollFactor); if (animated && ApplicationSettings::getInstance()->getUseAnimations() && area->isVisible()) { QPropertyAnimation* anim = new QPropertyAnimation(scrollBar, "value"); diff --git a/src/lib_gui/qt/element/code/QtCodeNavigateable.h b/src/lib_gui/qt/element/code/QtCodeNavigateable.h index afcee07c..07867873 100644 --- a/src/lib_gui/qt/element/code/QtCodeNavigateable.h +++ b/src/lib_gui/qt/element/code/QtCodeNavigateable.h @@ -3,6 +3,7 @@ #include +#include "CodeFocusHandler.h" #include "CodeScrollParams.h" #include "CodeSnippetParams.h" #include "types.h" @@ -28,14 +29,21 @@ public: const FilePath& filePath, size_t lineNumber, Id locationId, + Id scopeLocationId, bool animated, - CodeScrollParams::Target target) = 0; + CodeScrollParams::Target target, + bool focusTarget) = 0; virtual void onWindowFocus() = 0; virtual void findScreenMatches( const std::wstring& query, std::vector>* screenMatches) = 0; + virtual void setFocus(Id locationId) = 0; + virtual void setFocusOnTop() = 0; + virtual void moveFocus( + const CodeFocusHandler::Focus& focus, CodeFocusHandler::Direction direction) = 0; + protected: void ensureWidgetVisibleAnimated( const QWidget* parentWidget, diff --git a/src/lib_gui/qt/element/code/QtCodeNavigator.cpp b/src/lib_gui/qt/element/code/QtCodeNavigator.cpp index 60bfdaba..c36af3f2 100644 --- a/src/lib_gui/qt/element/code/QtCodeNavigator.cpp +++ b/src/lib_gui/qt/element/code/QtCodeNavigator.cpp @@ -1,16 +1,23 @@ #include "QtCodeNavigator.h" +#include #include #include #include #include +#include #include #include #include "ApplicationSettings.h" +#include "CodeFocusHandler.h" #include "MessageCodeReference.h" +#include "MessageFocusView.h" +#include "MessageHistoryRedo.h" +#include "MessageHistoryUndo.h" #include "MessageScrollCode.h" -#include "MessageShowError.h" +#include "MessageTabOpenWith.h" +#include "MessageToNextCodeReference.h" #include "QtCodeArea.h" #include "QtCodeFile.h" #include "QtCodeSnippet.h" @@ -33,13 +40,23 @@ QtCodeNavigator::QtCodeNavigator(QWidget* parent) layout->setAlignment(Qt::AlignTop); setLayout(layout); + const size_t indicatorHeight = 3; + + { + m_focusIndicator = new QWidget(this); + m_focusIndicator->setObjectName(QStringLiteral("focus_indicator")); + m_focusIndicator->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Fixed); + m_focusIndicator->setFixedHeight(indicatorHeight); + layout->addWidget(m_focusIndicator); + } + { QWidget* navigation = new QWidget(); navigation->setObjectName(QStringLiteral("code_navigation")); QHBoxLayout* navLayout = new QHBoxLayout(); navLayout->setSpacing(2); - navLayout->setContentsMargins(7, 7, 7, 6); + navLayout->setContentsMargins(7, 7 - indicatorHeight, 7, 6); { m_prevReferenceButton = new QtSearchBarButton( @@ -165,6 +182,9 @@ QtCodeNavigator::QtCodeNavigator(QWidget* parent) setMode(ApplicationSettings::getInstance()->getCodeViewModeSingle() ? MODE_SINGLE : MODE_LIST); updateFiles(); + + QApplication* app = dynamic_cast(QCoreApplication::instance()); + connect(app, &QApplication::focusChanged, this, &QtCodeNavigator::focusChanged); } QtCodeNavigator::~QtCodeNavigator() {} @@ -237,7 +257,7 @@ void QtCodeNavigator::clear() m_currentActiveTokenIds.clear(); m_activeTokenIds.clear(); m_activeLocalTokenIds.clear(); - m_focusedTokenIds.clear(); + m_coFocusedTokenIds.clear(); m_errorInfos.clear(); updateReferenceCount(0, 0, 0, 0); @@ -246,12 +266,14 @@ void QtCodeNavigator::clear() void QtCodeNavigator::clearSnippets() { clearScreenMatches(); + clearCurrentFocus(); m_list->clear(); } void QtCodeNavigator::clearFile() { clearScreenMatches(); + clearCurrentFocus(); m_single->clearFile(); } @@ -352,14 +374,14 @@ void QtCodeNavigator::setActiveLocalTokenIds( m_activeLocalTokenIds = std::set(activeLocalTokenIds.begin(), activeLocalTokenIds.end()); } -const std::set& QtCodeNavigator::getFocusedTokenIds() const +const std::set& QtCodeNavigator::getCoFocusedTokenIds() const { - return m_focusedTokenIds; + return m_coFocusedTokenIds; } -void QtCodeNavigator::setFocusedTokenIds(const std::vector& focusedTokenIds) +void QtCodeNavigator::setCoFocusedTokenIds(const std::vector& coFocusedTokenIds) { - m_focusedTokenIds = std::set(focusedTokenIds.begin(), focusedTokenIds.end()); + m_coFocusedTokenIds = std::set(coFocusedTokenIds.begin(), coFocusedTokenIds.end()); } std::wstring QtCodeNavigator::getErrorMessageForId(Id errorId) const @@ -413,18 +435,75 @@ bool QtCodeNavigator::hasSingleFileCached(const FilePath& filePath) const return m_single->hasFileCached(filePath); } -void QtCodeNavigator::focusTokenIds(const std::vector& focusedTokenIds) +void QtCodeNavigator::coFocusTokenIds(const std::vector& coFocusedTokenIds) { - setFocusedTokenIds(focusedTokenIds); + setCoFocusedTokenIds(coFocusedTokenIds); updateFiles(); } -void QtCodeNavigator::defocusTokenIds() +void QtCodeNavigator::deCoFocusTokenIds() { - setFocusedTokenIds({}); + setCoFocusedTokenIds({}); updateFiles(); } +void QtCodeNavigator::setNavigationFocus(bool focus) +{ + if (focus) + { + setFocus(); + CodeFocusHandler::focus(); + } + else + { + CodeFocusHandler::defocus(); + } + + m_focusIndicator->setProperty("focused", focus); + m_focusIndicator->style()->polish( + m_focusIndicator); // recomputes style to make property take effect +} + +void QtCodeNavigator::focusInitialLocation(Id locationId) +{ + if (locationId) + { + if (!isFocused()) + { + setNavigationFocus(true); + } + + m_current->setFocus(locationId); + return; + } + + if (hasCurrentFocus()) + { + return; + } + + if (m_mode == MODE_LIST) + { + const std::pair result = m_list->getFirstSnippetAndActiveLocationId(); + if (result.second) + { + result.first->setFocus(result.second); + return; + } + } + else + { + const Id locationId = m_single->getLocationIdOfFirstActiveLocationOfTokenId(0); + if (locationId) + { + m_single->setFocus(locationId); + return; + } + } + + m_current->setFocusOnTop(); +} + void QtCodeNavigator::updateFiles() { m_current->updateFiles(); @@ -483,9 +562,11 @@ void QtCodeNavigator::activateScreenMatch(size_t matchIndex) scrollTo( CodeScrollParams::toReference( - p.first->getSourceLocationFile()->getFilePath(), + p.first->getFilePath(), m_activeScreenMatchId, + 0, CodeScrollParams::Target::CENTER), + true, true); } @@ -523,7 +604,7 @@ void QtCodeNavigator::clearScreenMatches() m_screenMatches.clear(); } -void QtCodeNavigator::scrollTo(const CodeScrollParams& params, bool animated) +void QtCodeNavigator::scrollTo(const CodeScrollParams& params, bool animated, bool focusTarget) { if (!isVisible()) { @@ -537,15 +618,16 @@ void QtCodeNavigator::scrollTo(const CodeScrollParams& params, bool animated) { case CodeScrollParams::Type::TO_REFERENCE: func = [=]() { - m_current->scrollTo(params.filePath, 0, params.locationId, animated, params.target); + m_current->scrollTo( + params.filePath, 0, params.locationId, params.scopeLocationId, animated, params.target, focusTarget); }; break; case CodeScrollParams::Type::TO_FILE: - func = [=]() { m_current->scrollTo(params.filePath, 0, 0, animated, params.target); }; + func = [=]() { m_current->scrollTo(params.filePath, 0, 0, 0, animated, params.target, focusTarget); }; break; case CodeScrollParams::Type::TO_LINE: func = [=]() { - m_current->scrollTo(params.filePath, params.line, 0, animated, params.target); + m_current->scrollTo(params.filePath, params.line, 0, 0, animated, params.target, focusTarget); }; break; case CodeScrollParams::Type::TO_VALUE: @@ -576,6 +658,24 @@ void QtCodeNavigator::scrollTo(const CodeScrollParams& params, bool animated) m_scrollParams = CodeScrollParams(); } +void QtCodeNavigator::scrollToFocus() +{ + const CodeFocusHandler::Focus& focus = getCurrentFocus(); + + if (focus.file) + { + scrollTo(CodeScrollParams::toFile(focus.file->getFilePath(), CodeScrollParams::Target::VISIBLE), true, false); + } + else if (focus.scopeLine) + { + scrollTo(CodeScrollParams::toLine(focus.area->getFilePath(), focus.lineNumber, CodeScrollParams::Target::VISIBLE), true, false); + } + else if (focus.locationId) + { + scrollTo(CodeScrollParams::toReference(focus.area->getFilePath(), focus.locationId, 0, CodeScrollParams::Target::VISIBLE), true, false); + } +} + void QtCodeNavigator::scrolled(int value) { MessageScrollCode(value, m_mode == MODE_LIST).dispatch(); @@ -583,7 +683,149 @@ void QtCodeNavigator::scrolled(int value) void QtCodeNavigator::showEvent(QShowEvent* event) { - scrollTo(m_scrollParams, false); + scrollTo(m_scrollParams, false, true); +} + +void QtCodeNavigator::keyPressEvent(QKeyEvent* event) +{ + bool shift = event->modifiers() & Qt::ShiftModifier; + bool alt = event->modifiers() & Qt::AltModifier; + bool ctrl = event->modifiers() & Qt::ControlModifier; + const CodeFocusHandler::Focus& currentFocus = getCurrentFocus(); + + FilePath currentFilePath; + if (currentFocus.file) + { + currentFilePath = currentFocus.file->getFilePath(); + } + else if (currentFocus.area) + { + currentFilePath = currentFocus.area->getFilePath(); + } + + switch (event->key()) + { + case Qt::Key_Up: + case Qt::Key_K: + case Qt::Key_W: + if (!alt && !ctrl) + { + if (shift) + { + MessageToNextCodeReference(currentFilePath, currentFocus.lineNumber, false).dispatch(); + } + else + { + m_current->moveFocus(currentFocus, CodeFocusHandler::Direction::UP); + scrollToFocus(); + } + } + break; + + case Qt::Key_Down: + case Qt::Key_J: + case Qt::Key_S: + if (!alt && !ctrl) + { + if (shift) + { + MessageToNextCodeReference(currentFilePath, currentFocus.lineNumber, true).dispatch(); + } + else + { + m_current->moveFocus(currentFocus, CodeFocusHandler::Direction::DOWN); + scrollToFocus(); + } + } + break; + + case Qt::Key_Left: + case Qt::Key_H: + case Qt::Key_A: + if (!alt && !ctrl) + { + m_current->moveFocus(currentFocus, CodeFocusHandler::Direction::LEFT); + scrollToFocus(); + } + break; + + case Qt::Key_Right: + case Qt::Key_L: + case Qt::Key_D: + if (!alt && !ctrl) + { + m_current->moveFocus(currentFocus, CodeFocusHandler::Direction::RIGHT); + scrollToFocus(); + } + break; + + case Qt::Key_E: + case Qt::Key_Return: + if (currentFocus.area && currentFocus.locationId) + { + if (ctrl && shift) + { + MessageTabOpenWith(0, currentFocus.locationId).dispatch(); + } + else + { + currentFocus.area->activateLocationId(currentFocus.locationId, false); + } + } + else if (currentFocus.scopeLine) + { + currentFocus.scopeLine->clicked(); + } + else if (currentFocus.file) + { + currentFocus.file->toggleCollapsed(); + } + break; + + case Qt::Key_Y: + case Qt::Key_Z: + if (!alt && !ctrl) + { + if (shift) + { + MessageHistoryRedo().dispatch(); + } + else + { + MessageHistoryUndo().dispatch(); + } + } + break; + + default: + QWidget::keyPressEvent(event); + return; + } +} + +void QtCodeNavigator::focusInEvent(QFocusEvent* event) +{ + emit focusIn(); +} + +void QtCodeNavigator::focusOutEvent(QFocusEvent* event) +{ + QApplication* app = dynamic_cast(QCoreApplication::instance()); + if (isAncestorOf(app->focusWidget())) + { + return; + } + + emit focusOut(); +} + +void QtCodeNavigator::focusChanged(QWidget* from, QWidget* to) +{ + if (isAncestorOf(to)) + { + setFocus(); + emit focusIn(); + } } void QtCodeNavigator::previousReference() @@ -614,6 +856,9 @@ void QtCodeNavigator::setModeList() } m_single->clickedSnippetButton(); + + clearCurrentFocus(); + CodeFocusHandler::focusView(); } void QtCodeNavigator::setModeSingle() @@ -632,6 +877,9 @@ void QtCodeNavigator::setModeSingle() { m_list->maximizeFirstFile(); } + + clearCurrentFocus(); + CodeFocusHandler::focusView(); } void QtCodeNavigator::handleMessage(MessageWindowFocus* message) diff --git a/src/lib_gui/qt/element/code/QtCodeNavigator.h b/src/lib_gui/qt/element/code/QtCodeNavigator.h index e9c8248b..fc1850a2 100644 --- a/src/lib_gui/qt/element/code/QtCodeNavigator.h +++ b/src/lib_gui/qt/element/code/QtCodeNavigator.h @@ -3,6 +3,7 @@ #include +#include "CodeFocusHandler.h" #include "ErrorInfo.h" #include "LocationType.h" #include "MessageListener.h" @@ -19,6 +20,7 @@ class SourceLocationFile; class QtCodeNavigator : public QWidget + , public CodeFocusHandler , public MessageListener { Q_OBJECT @@ -69,8 +71,8 @@ public: const std::set& getActiveLocalTokenIds() const; void setActiveLocalTokenIds(const std::vector& activeLocalTokenIds, LocationType locationType); - const std::set& getFocusedTokenIds() const; - void setFocusedTokenIds(const std::vector& focusedTokenIds); + const std::set& getCoFocusedTokenIds() const; + void setCoFocusedTokenIds(const std::vector& coFocusedTokenIds); std::wstring getErrorMessageForId(Id errorId) const; void setErrorInfos(const std::vector& errorInfos); @@ -81,10 +83,13 @@ public: bool isInListMode() const; bool hasSingleFileCached(const FilePath& filePath) const; - void focusTokenIds(const std::vector& focusedTokenIds); - void defocusTokenIds(); + void coFocusTokenIds(const std::vector& coFocusedTokenIds); + void deCoFocusTokenIds(); - void updateFiles(); + void setNavigationFocus(bool focus); + void focusInitialLocation(Id locationId); + + void updateFiles() override; void refreshStyle(); @@ -94,15 +99,26 @@ public: bool hasScreenMatches() const; void clearScreenMatches(); - void scrollTo(const CodeScrollParams& params, bool animated); + void scrollTo(const CodeScrollParams& params, bool animated, bool focusTarget); + void scrollToFocus(); public slots: void scrolled(int value); +signals: + void focusIn(); + void focusOut(); + protected: void showEvent(QShowEvent* event) override; + void keyPressEvent(QKeyEvent* event) override; + + void focusInEvent(QFocusEvent* event) override; + void focusOutEvent(QFocusEvent* event) override; private slots: + void focusChanged(QWidget* from, QWidget* to); + void previousReference(); void nextReference(); @@ -132,9 +148,12 @@ private: std::set m_activeTokenIds; std::set m_activeLocalTokenIds; - std::set m_focusedTokenIds; + std::set m_coFocusedTokenIds; + std::map m_errorInfos; + QWidget* m_focusIndicator; + QtSearchBarButton* m_prevReferenceButton; QtSearchBarButton* m_nextReferenceButton; QLabel* m_refLabel; diff --git a/src/lib_gui/qt/element/code/QtCodeSnippet.cpp b/src/lib_gui/qt/element/code/QtCodeSnippet.cpp index 3ec5f794..430086ed 100644 --- a/src/lib_gui/qt/element/code/QtCodeSnippet.cpp +++ b/src/lib_gui/qt/element/code/QtCodeSnippet.cpp @@ -2,12 +2,14 @@ #include #include +#include #include #include "MessageShowScope.h" #include "QtCodeFile.h" #include "QtCodeNavigator.h" +#include "QtHoverButton.h" #include "SourceLocationFile.h" QtCodeSnippet::QtCodeSnippet(const CodeSnippetParams& params, QtCodeNavigator* navigator, QtCodeFile* file) @@ -18,9 +20,6 @@ QtCodeSnippet::QtCodeSnippet(const CodeSnippetParams& params, QtCodeNavigator* n , m_titleString(params.title) , m_footerId(params.footerId) , m_footerString(params.footer) - , m_title(nullptr) - , m_footer(nullptr) - , m_codeArea(nullptr) { setObjectName(QStringLiteral("code_snippet")); @@ -33,6 +32,7 @@ QtCodeSnippet::QtCodeSnippet(const CodeSnippetParams& params, QtCodeNavigator* n if (!m_titleString.empty() && !params.isOverview) { m_title = createScopeLine(layout); + m_titleDots = m_dots.back(); if (m_titleId == 0) // title is a file path { m_title->setText(QString::fromStdWString(FilePath(m_titleString).fileName())); @@ -51,6 +51,7 @@ QtCodeSnippet::QtCodeSnippet(const CodeSnippetParams& params, QtCodeNavigator* n if (!m_footerString.empty()) { m_footer = createScopeLine(layout); + m_footerDots = m_dots.back(); if (m_footerId == 0) // footer is a file path { m_footer->setText(QString::fromStdWString(FilePath(m_footerString).fileName())); @@ -107,6 +108,9 @@ void QtCodeSnippet::updateContent() { m_codeArea->updateContent(); updateDots(); + + updateScopeLineFocus(m_title, m_titleDots); + updateScopeLineFocus(m_footer, m_footerDots); } void QtCodeSnippet::setIsActiveFile(bool isActiveFile) @@ -151,6 +155,78 @@ void QtCodeSnippet::findScreenMatches( m_codeArea->findScreenMatches(query, screenMatches); } +bool QtCodeSnippet::hasFocus(const CodeFocusHandler::Focus& focus) const +{ + return m_codeArea == focus.area; +} + +bool QtCodeSnippet::setFocus(Id locationId) +{ + return m_codeArea->setFocus(locationId); +} + +bool QtCodeSnippet::moveFocus(const CodeFocusHandler::Focus& focus, CodeFocusHandler::Direction direction) +{ + if (m_codeArea == focus.area) + { + if (focus.scopeLine) + { + if (m_title == focus.scopeLine && direction == CodeFocusHandler::Direction::DOWN) + { + return m_codeArea->moveFocus(direction, m_codeArea->getStartLineNumber() - 1, 0); + } + else if (m_footer == focus.scopeLine && direction == CodeFocusHandler::Direction::UP) + { + return m_codeArea->moveFocus(direction, m_codeArea->getEndLineNumber() + 1, 0); + } + } + else + { + bool moved = m_codeArea->moveFocus(direction, focus.lineNumber, focus.locationId); + if (!moved) + { + if (m_title && direction == CodeFocusHandler::Direction::UP) + { + m_navigator->setFocusedScopeLine(m_codeArea, m_title, getStartLineNumber()); + return true; + } + else if (m_footer && direction == CodeFocusHandler::Direction::DOWN) + { + m_navigator->setFocusedScopeLine(m_codeArea, m_footer, getEndLineNumber()); + return true; + } + } + return moved; + } + } + return false; +} + +void QtCodeSnippet::focusTop() +{ + if (m_title) + { + m_navigator->setFocusedScopeLine(m_codeArea, m_title, getStartLineNumber()); + } + else + { + m_codeArea->moveFocus( + CodeFocusHandler::Direction::DOWN, m_codeArea->getStartLineNumber() - 1, 0); + } +} + +void QtCodeSnippet::focusBottom() +{ + if (m_footer) + { + m_navigator->setFocusedScopeLine(m_codeArea, m_footer, getEndLineNumber()); + } + else + { + m_codeArea->moveFocus(CodeFocusHandler::Direction::UP, m_codeArea->getEndLineNumber() + 1, 0); + } +} + void QtCodeSnippet::ensureLocationIdVisible(Id locationId, bool animated) { m_codeArea->ensureLocationIdVisible(locationId, width(), animated); @@ -158,6 +234,8 @@ void QtCodeSnippet::ensureLocationIdVisible(Id locationId, bool animated) void QtCodeSnippet::clickedTitle() { + m_codeArea->moveFocusToLine(static_cast(m_codeArea->getStartLineNumber()), 0, false); + if (m_titleId > 0) { MessageShowScope(m_titleId, m_navigator->hasErrors()).dispatch(); @@ -170,6 +248,8 @@ void QtCodeSnippet::clickedTitle() void QtCodeSnippet::clickedFooter() { + m_codeArea->moveFocusToLine(static_cast(m_codeArea->getEndLineNumber()), 0, true); + if (m_footerId > 0) { MessageShowScope(m_footerId, m_navigator->hasErrors()).dispatch(); @@ -180,7 +260,7 @@ void QtCodeSnippet::clickedFooter() } } -QPushButton* QtCodeSnippet::createScopeLine(QBoxLayout* layout) +QtHoverButton* QtCodeSnippet::createScopeLine(QBoxLayout* layout) { QHBoxLayout* lineLayout = new QHBoxLayout(); lineLayout->setMargin(0); @@ -194,13 +274,18 @@ QPushButton* QtCodeSnippet::createScopeLine(QBoxLayout* layout) lineLayout->addWidget(dots); m_dots.push_back(dots); - QPushButton* line = new QPushButton(this); + QtHoverButton* line = new QtHoverButton(this); line->setObjectName(QStringLiteral("scope_name")); line->minimumSizeHint(); // force font loading line->setAttribute(Qt::WA_LayoutUsesWidgetRect); // fixes layouting on Mac line->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); lineLayout->addWidget(line); + connect(line, &QtHoverButton::hoveredIn, [this, line]() { + m_navigator->setFocusedScopeLine(m_codeArea, line, 0); + m_navigator->setFocus(); + }); + return line; } @@ -212,3 +297,19 @@ void QtCodeSnippet::updateDots() dots->setMinimumWidth(m_codeArea->lineNumberAreaWidth()); } } + +void QtCodeSnippet::updateScopeLineFocus(QPushButton* line, QPushButton* dots) +{ + if (line && dots) + { + bool focus = line == m_navigator->getCurrentFocus().scopeLine; + if (focus != dots->property("focused").toBool()) + { + line->setProperty("focused", focus); + line->style()->polish(line); // recomputes style to make property take effect + + dots->setProperty("focused", focus); + dots->style()->polish(dots); // recomputes style to make property take effect + } + } +} diff --git a/src/lib_gui/qt/element/code/QtCodeSnippet.h b/src/lib_gui/qt/element/code/QtCodeSnippet.h index 10798efb..bba05b4b 100644 --- a/src/lib_gui/qt/element/code/QtCodeSnippet.h +++ b/src/lib_gui/qt/element/code/QtCodeSnippet.h @@ -9,12 +9,14 @@ #include "QtCodeArea.h" +#include "CodeFocusHandler.h" #include "CodeSnippetParams.h" class QBoxLayout; class QPushButton; class QtCodeFile; class QtCodeNavigator; +class QtHoverButton; class SourceLocationFile; class QtCodeSnippet: public QFrame @@ -51,6 +53,12 @@ public: void findScreenMatches( const std::wstring& query, std::vector>* screenMatches); + bool hasFocus(const CodeFocusHandler::Focus& focus) const; + bool setFocus(Id locationId); + bool moveFocus(const CodeFocusHandler::Focus& focus, CodeFocusHandler::Direction direction); + void focusTop(); + void focusBottom(); + void ensureLocationIdVisible(Id locationId, bool animated); private slots: @@ -58,8 +66,9 @@ private slots: void clickedFooter(); private: - QPushButton* createScopeLine(QBoxLayout* layout); + QtHoverButton* createScopeLine(QBoxLayout* layout); void updateDots(); + void updateScopeLineFocus(QPushButton* line, QPushButton* dots); QtCodeNavigator* m_navigator; QtCodeFile* m_file; @@ -72,9 +81,13 @@ private: std::vector m_dots; - QPushButton* m_title; - QPushButton* m_footer; - QtCodeArea* m_codeArea; + QtHoverButton* m_title = nullptr; + QtHoverButton* m_footer = nullptr; + + QPushButton* m_titleDots = nullptr; + QPushButton* m_footerDots = nullptr; + + QtCodeArea* m_codeArea = nullptr; }; #endif // QT_CODE_SNIPPET_H diff --git a/src/lib_gui/qt/element/search/QtSmartSearchBox.cpp b/src/lib_gui/qt/element/search/QtSmartSearchBox.cpp index 28cf9fdb..e4bbc83c 100644 --- a/src/lib_gui/qt/element/search/QtSmartSearchBox.cpp +++ b/src/lib_gui/qt/element/search/QtSmartSearchBox.cpp @@ -10,6 +10,7 @@ #include "ColorScheme.h" #include "GraphViewStyle.h" +#include "MessageFocusedSearchView.h" #include "NodeTypeSet.h" #include "utility.h" #include "utilityString.h" @@ -224,6 +225,7 @@ void QtSmartSearchBox::resizeEvent(QResizeEvent* event) void QtSmartSearchBox::focusInEvent(QFocusEvent* event) { QLineEdit::focusInEvent(event); + MessageFocusedSearchView(true).dispatch(); if (event->reason() != Qt::MouseFocusReason && event->reason() != Qt::ShortcutFocusReason) { @@ -257,6 +259,12 @@ void QtSmartSearchBox::focusInEvent(QFocusEvent* event) } } +void QtSmartSearchBox::focusOutEvent(QFocusEvent* event) +{ + QLineEdit::focusOutEvent(event); + MessageFocusedSearchView(false).dispatch(); +} + void QtSmartSearchBox::keyPressEvent(QKeyEvent* event) { m_shiftKeyDown = event->modifiers() & Qt::ShiftModifier; diff --git a/src/lib_gui/qt/element/search/QtSmartSearchBox.h b/src/lib_gui/qt/element/search/QtSmartSearchBox.h index a417e906..9fc4c8b0 100644 --- a/src/lib_gui/qt/element/search/QtSmartSearchBox.h +++ b/src/lib_gui/qt/element/search/QtSmartSearchBox.h @@ -56,7 +56,9 @@ public: protected: virtual bool event(QEvent* event); virtual void resizeEvent(QResizeEvent* event); + virtual void focusInEvent(QFocusEvent* event); + virtual void focusOutEvent(QFocusEvent* event); virtual void keyPressEvent(QKeyEvent* event); virtual void keyReleaseEvent(QKeyEvent* event); diff --git a/src/lib_gui/qt/graphics/GraphFocusHandler.cpp b/src/lib_gui/qt/graphics/GraphFocusHandler.cpp new file mode 100644 index 00000000..a2550e25 --- /dev/null +++ b/src/lib_gui/qt/graphics/GraphFocusHandler.cpp @@ -0,0 +1,573 @@ +#include "GraphFocusHandler.h" + +#include "MessageFocusChanged.h" +#include "QtGraphEdge.h" +#include "QtGraphNode.h" +#include "utility.h" + +GraphFocusHandler::GraphFocusHandler(GraphFocusClient* client): m_client(client) {} + +void GraphFocusHandler::clear() +{ + m_focusNode = nullptr; + m_focusEdge = nullptr; + m_lastFocusId = 0; +} + +void GraphFocusHandler::focus() +{ + if (m_focusNode) + { + m_focusNode->setIsFocused(true); + } + else if (m_focusEdge) + { + m_focusEdge->setIsFocused(true); + } + else + { + focusInitialNode(); + } +} + +void GraphFocusHandler::defocus() +{ + if (m_focusNode) + { + m_focusNode->setIsFocused(false); + } + else if (m_focusEdge) + { + m_focusEdge->setIsFocused(false); + } +} + +void GraphFocusHandler::focusInitialNode() +{ + if (m_focusNode != nullptr || m_focusEdge != nullptr) + { + return; + } + + QtGraphNode* nodeToFocus = nullptr; + if (m_lastFocusId) + { + nodeToFocus = QtGraphNode::findNodeRecursive(m_client->getGraphNodes(), m_lastFocusId); + } + + if (!nodeToFocus) + { + nodeToFocus = m_client->getActiveNode(); + } + + if (!nodeToFocus) + { + nodeToFocus = findChildNodeRecursive(m_client->getGraphNodes(), true); + } + + if (nodeToFocus) + { + focusNode(nodeToFocus); + } +} + +void GraphFocusHandler::focusTokenId( + const std::list& nodes, const std::list& edges, Id tokenId) +{ + m_focusNode = nullptr; + m_focusEdge = nullptr; + + QtGraphNode* nodeToFocus = QtGraphNode::findNodeRecursive(nodes, tokenId); + if (nodeToFocus) + { + focusNode(nodeToFocus); + return; + } + + QtGraphEdge* edgeToFocus = nullptr; + for (QtGraphEdge* edge: edges) + { + if (edge->getTokenId() == tokenId) + { + edgeToFocus = edge; + break; + } + } + + if (edgeToFocus) + { + focusEdge(edgeToFocus); + } +} + +void GraphFocusHandler::refocusNode( + const std::list& newNodes, Id oldActiveTokenId, Id newActiveTokenId) +{ + m_focusNode = nullptr; + m_focusEdge = nullptr; + + if (m_lastFocusId && (m_lastFocusId == newActiveTokenId || oldActiveTokenId == newActiveTokenId)) + { + QtGraphNode* nodeToFocus = QtGraphNode::findNodeRecursive(newNodes, m_lastFocusId); + if (nodeToFocus) + { + m_focusNode = nodeToFocus; + nodeToFocus->setIsFocused(true); + } + } + else + { + m_lastFocusId = 0; + } +} + +void GraphFocusHandler::focusNext(Direction direction, bool navigateEdges) +{ + QtGraphEdge* nextEdge = nullptr; + + if (m_focusNode) + { + if (navigateEdges) + { + nextEdge = findNextEdge(m_focusNode->sceneBoundingRect().center(), direction); + if (nextEdge) + { + focusEdge(nextEdge); + } + } + + if (!nextEdge) + { + QtGraphNode* nextNode = findNextNode(m_focusNode, direction); + if (nextNode) + { + focusNode(nextNode); + } + } + } + else if (m_focusEdge) + { + if (navigateEdges) + { + nextEdge = findNextEdge(m_focusEdge->getBoundingRect().center(), direction, m_focusEdge); + if (nextEdge) + { + focusEdge(nextEdge); + } + } + + if (!nextEdge) + { + QtGraphNode* nextNode = findNextNode(m_focusEdge, direction); + if (nextNode) + { + focusNode(nextNode); + } + } + } + + if (m_focusNode) + { + m_client->ensureNodeVisible(m_focusNode); + } +} + +void GraphFocusHandler::focusNode(QtGraphNode* node) +{ + m_client->focusView(true); + + if (node == m_focusNode) + { + return; + } + + defocusGraph(); + + if (node->isFocusable()) + { + node->setIsFocused(true); + m_focusNode = node; + m_lastFocusId = node->getTokenId(); + + MessageFocusChanged(MessageFocusChanged::ViewType::GRAPH, node->getTokenId()).dispatch(); + } +} + +void GraphFocusHandler::defocusNode(QtGraphNode* node) +{ + QtGraphNode* parent = node->getParent(); + while (parent && !parent->isFocusable()) + { + parent = parent->getParent(); + } + + if (parent) + { + focusNode(parent); + } +} + +void GraphFocusHandler::focusEdge(QtGraphEdge* edge) +{ + m_client->focusView(true); + + if (edge == m_focusEdge) + { + return; + } + + defocusGraph(); + + if (edge->isFocusable()) + { + edge->setIsFocused(true); + m_focusEdge = edge; + m_lastFocusId = 0; + + MessageFocusChanged(MessageFocusChanged::ViewType::GRAPH, edge->getTokenId()).dispatch(); + } +} + +void GraphFocusHandler::defocusEdge(QtGraphEdge* edge) {} + +void GraphFocusHandler::defocusGraph() +{ + if (m_focusNode) + { + m_focusNode->setIsFocused(false); + m_focusNode = nullptr; + } + + if (m_focusEdge) + { + m_focusEdge->setIsFocused(false); + m_focusEdge = nullptr; + } +} + +void GraphFocusHandler::activateFocus(bool openInTab) +{ + if (m_focusNode) + { + if (openInTab) + { + m_focusNode->onMiddleClick(); + } + else + { + m_focusNode->onClick(); + } + } + else if (m_focusEdge) + { + m_focusEdge->onClick(); + } +} + +void GraphFocusHandler::expandFocus() +{ + if (m_focusNode) + { + m_lastFocusId = m_focusNode->onCollapseExpand(); + } + else if (m_focusEdge) + { + m_focusEdge->onClick(); + } +} + +QtGraphNode* GraphFocusHandler::findNextNode(QtGraphNode* node, Direction direction) +{ + switch (direction) + { + case Direction::UP: + { + QtGraphNode* nextNode = findSibling(node, direction); + if (nextNode) + { + QtGraphNode* childNode = nextNode; + QtGraphNode* lastChildNode = nullptr; + while (childNode) + { + childNode = findChildNodeRecursive(childNode->getSubNodes(), false); + if (childNode) + { + lastChildNode = childNode; + } + } + + if (lastChildNode && lastChildNode->getPosition().y() < node->getPosition().y()) + { + return lastChildNode; + } + + return nextNode; + } + + QtGraphNode* parent = node->getParent(); + while (parent && !parent->isFocusable()) + { + parent = parent->getParent(); + } + + if (parent) + { + return parent; + } + break; + } + + case Direction::DOWN: + { + if (node->getSubNodes().size()) + { + QtGraphNode* nextNode = findChildNodeRecursive(node->getSubNodes(), true); + if (nextNode) + { + return nextNode; + } + } + } + + case Direction::LEFT: + case Direction::RIGHT: + { + QtGraphNode* nextNode = findSibling(node, direction); + if (nextNode) + { + return nextNode; + } + break; + } + } + + return nullptr; +} + +QtGraphNode* GraphFocusHandler::findNextNode(QtGraphEdge* edge, Direction direction) +{ + if (edge->isHorizontal()) + { + if ((direction == Direction::LEFT) == + (edge->getOwner()->getPosition().x() < edge->getTarget()->getPosition().x())) + { + return edge->getOwner(); + } + else + { + return edge->getTarget(); + } + } + else + { + if ((direction == Direction::UP) == + (edge->getOwner()->getPosition().y() < edge->getTarget()->getPosition().y())) + { + return edge->getOwner(); + } + else + { + return edge->getTarget(); + } + } + + return nullptr; +} + +QtGraphEdge* GraphFocusHandler::findNextEdge(QPointF pos, Direction direction, QtGraphEdge* previousEdge) +{ + QtGraphEdge* closestEdge = nullptr; + QRectF previousEdgeRect; + if (previousEdge) + { + previousEdgeRect = previousEdge->getBoundingRect(); + } + + qreal minDist = 0; + + for (QtGraphEdge* edge: m_client->getGraphEdges()) + { + if (edge == previousEdge || !edge->isFocusable()) + { + continue; + } + + QRectF edgeRect = edge->getBoundingRect(); + QPointF edgePos = edgeRect.center(); + + bool top = edgePos.y() < pos.y(); + bool bottom = edgePos.y() > pos.y(); + bool left = edgePos.x() < pos.x(); + bool right = edgePos.x() > pos.x(); + + bool isDir = (direction == Direction::UP && top) || + (direction == Direction::DOWN && bottom) || (direction == Direction::LEFT && left) || + (direction == Direction::RIGHT && right); + + if (isDir) + { + qreal distX = std::abs(pos.x() - edgePos.x()); + qreal distY = std::abs(pos.y() - edgePos.y()); + + const bool vertical = (direction == Direction::UP || direction == Direction::DOWN); + + qreal distXMult = vertical ? 2.0 : 1.0; + qreal distYMult = vertical ? 1.0 : 2.0; + + // only use one dimension for distance calculation in aggregation graphs to not miss + // certain edges + if (edge->isBezierEdge() && m_client->getGraphNodes().size() == 2) + { + distXMult = vertical ? 0.0 : 1.0; + distYMult = vertical ? 1.0 : 0.0; + } + + qreal dist = distX * distXMult + distY * distYMult; + if (!minDist || dist < minDist) + { + minDist = dist; + closestEdge = edge; + } + } + } + + return closestEdge; +} + +QtGraphNode* GraphFocusHandler::findChildNodeRecursive(const std::list& nodes, bool first) +{ + QtGraphNode* result = nullptr; + + for (QtGraphNode* node: nodes) + { + if (node->isFocusable()) + { + result = node; + if (first) + { + break; + } + } + + QtGraphNode* newResult = findChildNodeRecursive(node->getSubNodes(), first); + if (newResult) + { + result = newResult; + if (first) + { + break; + } + } + } + + return result; +} + +QtGraphNode* GraphFocusHandler::findSibling(const QtGraphNode* node, Direction direction) +{ + QtGraphNode* nextSibling = nullptr; + Vec2i pos = node->getPosition(); + Vec4i rect = node->getBoundingRect(); + int minDist = 0; + + for (auto siblings: getSiblingsHierarchyRecursive(node)) + { + for (QtGraphNode* sibling: siblings) + { + if (sibling == node || !sibling->isFocusable()) + { + continue; + } + + Vec4i rectS = sibling->getBoundingRect(); + + const bool top = rectS.w() < rect.y(); + const bool bottom = rectS.y() > rect.w(); + const bool left = rectS.z() < rect.x(); + const bool right = rectS.x() > rect.z(); + + const bool isDir = (direction == Direction::UP && + (top || (!top && !bottom && !left && !right))) || + (direction == Direction::DOWN && bottom) || + (direction == Direction::LEFT && left) || (direction == Direction::RIGHT && right); + + if (isDir) + { + int distX = 0; + int distY = 0; + + int distXMult = (direction == Direction::UP || direction == Direction::DOWN) ? 2 : 1; + int distYMult = (direction == Direction::LEFT || direction == Direction::RIGHT) ? 2 + : 1; + if (top) + distY = rect.y() - rectS.w(); + if (bottom) + distY = rectS.y() - rect.w(); + if (left) + distX = rect.x() - rectS.z(); + if (right) + distX = rectS.x() - rect.z(); + + int dist = distX * distXMult + distY * distYMult; + if (!nextSibling || dist < minDist) + { + nextSibling = sibling; + minDist = dist; + } + } + } + + if (nextSibling) + { + break; + } + } + + return nextSibling; +} + +std::vector> GraphFocusHandler::getSiblingsHierarchyRecursive( + const QtGraphNode* node) +{ + std::vector> siblingsList; + + QtGraphNode* parent = node->getParent(); + while (parent && !parent->isDataNode() && !parent->isGroupNode()) + { + parent = parent->getParent(); + } + + if (parent) + { + std::vector siblings; + addSiblingsRecursive(parent->getSubNodes(), siblings); + if (siblings.size()) + { + siblingsList.push_back(siblings); + } + + utility::append(siblingsList, getSiblingsHierarchyRecursive(parent)); + } + else + { + siblingsList.push_back(utility::toVector(m_client->getGraphNodes())); + } + + return siblingsList; +} + +void GraphFocusHandler::addSiblingsRecursive( + const std::list& nodes, std::vector& siblings) +{ + for (QtGraphNode* node: nodes) + { + if (node->isFocusable()) + { + siblings.push_back(node); + } + else + { + addSiblingsRecursive(node->getSubNodes(), siblings); + } + } +} diff --git a/src/lib_gui/qt/graphics/GraphFocusHandler.h b/src/lib_gui/qt/graphics/GraphFocusHandler.h new file mode 100644 index 00000000..d4922026 --- /dev/null +++ b/src/lib_gui/qt/graphics/GraphFocusHandler.h @@ -0,0 +1,79 @@ +#ifndef GRAPH_FOCUS_HANDLER_H +#define GRAPH_FOCUS_HANDLER_H + +#include +#include + +#include "types.h" + +class QtGraphEdge; +class QtGraphNode; +class QPointF; + +class GraphFocusClient +{ +public: + virtual void focusView(bool focusIn) = 0; + + virtual const std::list& getGraphNodes() const = 0; + virtual const std::list& getGraphEdges() const = 0; + + virtual QtGraphNode* getActiveNode() const = 0; + + virtual void ensureNodeVisible(QtGraphNode* node) = 0; +}; + +class GraphFocusHandler +{ +public: + enum class Direction + { + UP, + DOWN, + LEFT, + RIGHT + }; + + GraphFocusHandler(GraphFocusClient* client); + + void clear(); + + void focus(); + void defocus(); + + void focusInitialNode(); + void focusTokenId(const std::list& nodes, const std::list& edges, Id tokenId); + void refocusNode(const std::list& newNodes, Id oldActiveTokenId, Id newActiveTokenId); + + void focusNext(Direction direction, bool navigateEdges); + + void focusNode(QtGraphNode* node); + void defocusNode(QtGraphNode* node); + + void focusEdge(QtGraphEdge* edge); + void defocusEdge(QtGraphEdge* edge); + + void defocusGraph(); + + void activateFocus(bool openInTab); + void expandFocus(); + +private: + QtGraphNode* findNextNode(QtGraphNode* node, Direction direction); + QtGraphNode* findNextNode(QtGraphEdge* edge, Direction direction); + QtGraphEdge* findNextEdge(QPointF pos, Direction direction, QtGraphEdge* previousEdge = nullptr); + + QtGraphNode* findChildNodeRecursive(const std::list& nodes, bool first); + + QtGraphNode* findSibling(const QtGraphNode* node, Direction direction); + std::vector> getSiblingsHierarchyRecursive(const QtGraphNode* node); + void addSiblingsRecursive(const std::list& nodes, std::vector& siblings); + + GraphFocusClient* const m_client; + + QtGraphNode* m_focusNode = nullptr; + QtGraphEdge* m_focusEdge = nullptr; + Id m_lastFocusId = 0; +}; + +#endif // GRAPH_FOCUS_HANDLER_H diff --git a/src/lib_gui/qt/graphics/base/QtGraphicsView.cpp b/src/lib_gui/qt/graphics/QtGraphicsView.cpp similarity index 88% rename from src/lib_gui/qt/graphics/base/QtGraphicsView.cpp rename to src/lib_gui/qt/graphics/QtGraphicsView.cpp index 9319ba99..9e16e685 100644 --- a/src/lib_gui/qt/graphics/base/QtGraphicsView.cpp +++ b/src/lib_gui/qt/graphics/QtGraphicsView.cpp @@ -12,11 +12,15 @@ #include #include "ApplicationSettings.h" +#include "GraphFocusHandler.h" #include "MessageActivateLegend.h" #include "MessageBookmarkCreate.h" #include "MessageCodeShowDefinition.h" +#include "MessageFocusView.h" #include "MessageGraphNodeExpand.h" #include "MessageGraphNodeHide.h" +#include "MessageHistoryRedo.h" +#include "MessageHistoryUndo.h" #include "MessageTabOpenWith.h" #include "QtContextMenu.h" #include "QtFileDialog.h" @@ -30,15 +34,11 @@ #include "utilityApp.h" #include "utilityQt.h" -QtGraphicsView::QtGraphicsView(QWidget* parent) +QtGraphicsView::QtGraphicsView(GraphFocusHandler* focusHandler, QWidget* parent) : QGraphicsView(parent) + , m_focusHandler(focusHandler) , m_zoomFactor(1.0f) , m_appZoomFactor(1.0f) - , m_up(false) - , m_down(false) - , m_left(false) - , m_right(false) - , m_shift(false) , m_zoomInButtonSpeed(20.0f) , m_zoomOutButtonSpeed(-20.0f) { @@ -57,7 +57,10 @@ QtGraphicsView::QtGraphicsView(QWidget* parent) m_zoomLabelTimer = std::make_shared(this); connect(m_zoomLabelTimer.get(), &QTimer::timeout, this, &QtGraphicsView::hideZoomLabel); - m_openInTabAction = new QAction(QStringLiteral("Open in New Tab"), this); + m_openInTabAction = new QAction(QStringLiteral("Open in New Tab (Ctrl + Shift + Left Click)"), this); +#if defined(Q_OS_MAC) + m_openInTabAction->setText(QStringLiteral("Open in New Tab (Cmd + Shift + Left Click)")); +#endif m_openInTabAction->setStatusTip(QStringLiteral("Open this node in a new tab")); m_openInTabAction->setToolTip(QStringLiteral("Open this node in a new tab")); connect(m_openInTabAction, &QAction::triggered, this, &QtGraphicsView::openInTab); @@ -121,6 +124,10 @@ QtGraphicsView::QtGraphicsView(QWidget* parent) m_exportGraphAction->setToolTip(QStringLiteral("Save this graph as image file")); connect(m_exportGraphAction, &QAction::triggered, this, &QtGraphicsView::exportGraph); + m_focusIndicator = new QWidget(this); + m_focusIndicator->setObjectName(QStringLiteral("focus_indicator")); + m_focusIndicator->hide(); + m_zoomState = new QPushButton(this); m_zoomState->setObjectName(QStringLiteral("zoom_state")); m_zoomState->hide(); @@ -205,6 +212,8 @@ void QtGraphicsView::ensureVisibleAnimated(const QRectF& rect, int xmargin, int int xval = horizontalScrollBar()->value(); int yval = verticalScrollBar()->value(); + setInteractive(false); + ensureVisible(rect, xmargin, ymargin); if (ApplicationSettings::getInstance()->getUseAnimations() && isVisible()) @@ -231,8 +240,14 @@ void QtGraphicsView::ensureVisibleAnimated(const QRectF& rect, int xmargin, int yanim->setEasingCurve(QEasingCurve::InOutQuad); move->addAnimation(yanim); + connect(move, &QPropertyAnimation::finished, [this]() { setInteractive(true); }); + move->start(); } + else + { + setInteractive(true); + } } void QtGraphicsView::updateZoom(float delta) @@ -250,6 +265,7 @@ void QtGraphicsView::updateZoom(float delta) void QtGraphicsView::resizeEvent(QResizeEvent* event) { + m_focusIndicator->setGeometry(QRect(0, 0, event->size().width(), 3)); m_zoomState->setGeometry(QRect(31, event->size().height() - 27, 65, 19)); m_zoomInButton->setGeometry(QRect(8, event->size().height() - 50, 19, 19)); m_zoomOutButton->setGeometry(QRect(8, event->size().height() - 27, 18, 19)); @@ -292,34 +308,94 @@ void QtGraphicsView::mouseReleaseEvent(QMouseEvent* event) void QtGraphicsView::keyPressEvent(QKeyEvent* event) { - if (event->key() >= Qt::Key_A && event->key() <= Qt::Key_Z && event->text().size()) - { - QChar c = event->text().at(0).toUpper(); - emit characterKeyPressed(c); - } - bool moved = moves(); + bool shift = event->modifiers() & Qt::ShiftModifier; + bool alt = event->modifiers() & Qt::AltModifier; + bool ctrl = event->modifiers() & Qt::ControlModifier; switch (event->key()) { + case Qt::Key_Up: + case Qt::Key_K: case Qt::Key_W: - m_up = true; - break; - case Qt::Key_A: - m_left = true; + if (alt) + { + m_up = true; + } + else if (!ctrl) + { + m_focusHandler->focusNext(GraphFocusHandler::Direction::UP, shift); + } break; + + case Qt::Key_Down: + case Qt::Key_J: case Qt::Key_S: - m_down = true; + if (alt) + { + m_down = true; + } + else if (!ctrl) + { + m_focusHandler->focusNext(GraphFocusHandler::Direction::DOWN, shift); + } break; + + case Qt::Key_Left: + case Qt::Key_H: + case Qt::Key_A: + if (!alt && !ctrl) + { + m_focusHandler->focusNext(GraphFocusHandler::Direction::LEFT, shift); + } + break; + + case Qt::Key_Right: + case Qt::Key_L: case Qt::Key_D: - m_right = true; + if (!alt && !ctrl) + { + m_focusHandler->focusNext(GraphFocusHandler::Direction::RIGHT, shift); + } break; + + case Qt::Key_E: + case Qt::Key_Return: + if (ctrl && shift) + { + m_focusHandler->activateFocus(true); + } + else if (shift) + { + m_focusHandler->expandFocus(); + } + else + { + m_focusHandler->activateFocus(false); + } + break; + + case Qt::Key_Y: + case Qt::Key_Z: + if (!alt && !ctrl) + { + if (shift) + { + MessageHistoryRedo().dispatch(); + } + else + { + MessageHistoryUndo().dispatch(); + } + } + break; + case Qt::Key_0: setZoomFactor(1.0f); updateTransform(); break; - case Qt::Key_Shift: - m_shift = true; + case Qt::Key_Alt: + m_alt = true; break; default: QGraphicsView::keyPressEvent(event); @@ -341,17 +417,11 @@ void QtGraphicsView::keyReleaseEvent(QKeyEvent* event) case Qt::Key_W: m_up = false; break; - case Qt::Key_A: - m_left = false; - break; case Qt::Key_S: m_down = false; break; - case Qt::Key_D: - m_right = false; - break; - case Qt::Key_Shift: - m_shift = false; + case Qt::Key_Alt: + m_alt = false; break; default: return; @@ -493,61 +563,35 @@ void QtGraphicsView::contextMenuEvent(QContextMenuEvent* event) menu.show(); } +void QtGraphicsView::focusInEvent(QFocusEvent* event) +{ + m_focusIndicator->show(); + emit focusIn(); + + MessageFocusView(MessageFocusView::ViewType::GRAPH).dispatch(); +} + +void QtGraphicsView::focusOutEvent(QFocusEvent* event) +{ + m_focusIndicator->hide(); + emit focusOut(); +} + void QtGraphicsView::updateTimer() { - float ds = 30.0f; - float dz = 50.0f; + const float dz = 50.0f; - float x = 0.0f; - float y = 0.0f; - float z = 0.0f; - - if (m_shift) + if (m_alt) { if (m_up) { - z += dz; + updateZoom(dz); } else if (m_down) { - z -= dz; + updateZoom(-dz); } } - else - { - if (m_up) - { - y -= ds; - } - else if (m_down) - { - y += ds; - } - - if (m_left) - { - x -= ds; - } - else if (m_right) - { - x += ds; - } - } - - if (x != 0) - { - horizontalScrollBar()->setValue(static_cast(horizontalScrollBar()->value() + x)); - } - - if (y != 0) - { - verticalScrollBar()->setValue(static_cast(verticalScrollBar()->value() + y)); - } - - if (z != 0) - { - updateZoom(z); - } } void QtGraphicsView::stopTimer() @@ -704,7 +748,7 @@ void QtGraphicsView::legendClicked() bool QtGraphicsView::moves() const { - return m_up || m_down || m_left || m_right; + return m_up || m_down; } void QtGraphicsView::setZoomFactor(float zoomFactor) diff --git a/src/lib_gui/qt/graphics/base/QtGraphicsView.h b/src/lib_gui/qt/graphics/QtGraphicsView.h similarity index 87% rename from src/lib_gui/qt/graphics/base/QtGraphicsView.h rename to src/lib_gui/qt/graphics/QtGraphicsView.h index 3e68f117..fa15b4cc 100644 --- a/src/lib_gui/qt/graphics/base/QtGraphicsView.h +++ b/src/lib_gui/qt/graphics/QtGraphicsView.h @@ -7,6 +7,7 @@ #include "types.h" +class GraphFocusHandler; class QPushButton; class QTimer; class QtGraphEdge; @@ -18,7 +19,7 @@ class QtGraphicsView: public QGraphicsView Q_OBJECT public: - QtGraphicsView(QWidget* parent); + QtGraphicsView(GraphFocusHandler* focusHandler, QWidget* parent); float getZoomFactor() const; void setAppZoomFactor(float appZoomFactor); @@ -46,11 +47,16 @@ protected: void contextMenuEvent(QContextMenuEvent* event); + void focusInEvent(QFocusEvent* event); + void focusOutEvent(QFocusEvent* event); + signals: void emptySpaceClicked(); - void characterKeyPressed(QChar c); void resized(); + void focusIn(); + void focusOut(); + private slots: void updateTimer(); void stopTimer(); @@ -82,16 +88,16 @@ private: void setZoomFactor(float zoomFactor); void updateTransform(); + GraphFocusHandler* m_focusHandler; + QPoint m_last; float m_zoomFactor; float m_appZoomFactor; - bool m_up; - bool m_down; - bool m_left; - bool m_right; - bool m_shift; + bool m_up = false; + bool m_down = false; + bool m_alt = false; std::wstring m_clipboardNodeName; Id m_openInTabNodeId; @@ -118,6 +124,8 @@ private: QAction* m_exportGraphAction; + QWidget* m_focusIndicator; + QPushButton* m_zoomState; QtSelfRefreshIconButton* m_zoomInButton; QtSelfRefreshIconButton* m_zoomOutButton; diff --git a/src/lib_gui/qt/graphics/component/QtGraphNodeComponentClickable.cpp b/src/lib_gui/qt/graphics/component/QtGraphNodeComponentClickable.cpp index 8d6fcd1e..5bf77842 100644 --- a/src/lib_gui/qt/graphics/component/QtGraphNodeComponentClickable.cpp +++ b/src/lib_gui/qt/graphics/component/QtGraphNodeComponentClickable.cpp @@ -48,7 +48,13 @@ void QtGraphNodeComponentClickable::nodeMouseReleaseEvent(QGraphicsSceneMouseEve if (!m_mouseMoved) { - if (event->modifiers() & Qt::ShiftModifier && event->button() == Qt::LeftButton) + if ( + event->modifiers() & Qt::ControlModifier && event->modifiers() & Qt::ShiftModifier && + event->button() == Qt::LeftButton) + { + m_graphNode->onMiddleClick(); + } + else if (event->modifiers() & Qt::ShiftModifier && event->button() == Qt::LeftButton) { m_graphNode->onCollapseExpand(); } diff --git a/src/lib_gui/qt/graphics/graph/QtGraphEdge.cpp b/src/lib_gui/qt/graphics/graph/QtGraphEdge.cpp index ca3286c6..8c57a6d2 100644 --- a/src/lib_gui/qt/graphics/graph/QtGraphEdge.cpp +++ b/src/lib_gui/qt/graphics/graph/QtGraphEdge.cpp @@ -5,6 +5,7 @@ #include #include "Edge.h" +#include "GraphFocusHandler.h" #include "GraphViewStyle.h" #include "MessageActivateEdge.h" #include "MessageActivateTrailEdge.h" @@ -30,11 +31,12 @@ void QtGraphEdge::unfocusBezierEdge() { if (s_focusedBezierEdge) { - s_focusedBezierEdge->focusOut(); + s_focusedBezierEdge->coFocusOut(); } } QtGraphEdge::QtGraphEdge( + GraphFocusHandler* focusHandler, QtGraphNode* owner, QtGraphNode* target, const Edge* data, @@ -43,20 +45,15 @@ QtGraphEdge::QtGraphEdge( bool isInteractive, bool horizontal, TokenComponentAggregation::Direction direction) - : m_data(data) + : m_focusHandler(focusHandler) + , m_data(data) , m_owner(owner) , m_target(target) - , m_child(nullptr) , m_isActive(isActive) - , m_isFocused(false) , m_isHorizontal(horizontal) , m_weight(weight) , m_direction(direction) - , m_isTrailEdge(false) - , m_useBezier(false) , m_isInteractive(isInteractive) - , m_mousePos(0, 0) - , m_mouseMoved(false) { this->setCursor(Qt::PointingHandCursor); @@ -105,7 +102,7 @@ void QtGraphEdge::updateLine() Edge::EdgeType type = (getData() ? getData()->getType() : Edge::EDGE_AGGREGATION); GraphViewStyle::EdgeStyle style = GraphViewStyle::getStyleForEdgeType( - type, m_isActive | m_isFocused, false, m_isTrailEdge, isAmbiguous()); + type, m_isActive | m_isCoFocused, m_isFocused, m_isTrailEdge, isAmbiguous()); Vec4i ownerRect = owner->getBoundingRect(); Vec4i targetRect = target->getBoundingRect(); @@ -173,6 +170,7 @@ void QtGraphEdge::updateLine() bool showArrow = m_direction != TokenComponentAggregation::DIRECTION_NONE; QtLineItemBezier* bezier = new QtLineItemBezier(this); + m_child = bezier; bezier->updateLine( ownerRect, targetRect, ownerParentRect, targetParentRect, style, m_weight, showArrow); bezier->setRoute(route); @@ -311,9 +309,23 @@ void QtGraphEdge::setIsFocused(bool isFocused) } } +void QtGraphEdge::setIsCoFocused(bool isCoFocused) +{ + if (m_isCoFocused != isCoFocused) + { + m_isCoFocused = isCoFocused; + updateLine(); + } +} + +bool QtGraphEdge::isFocusable() const +{ + return m_isInteractive; +} + void QtGraphEdge::onClick() { - if (!getData() || m_owner->isGroupNode() || m_target->isGroupNode()) + if (isExpandable()) { QtGraphNode* node = (m_direction == TokenComponentAggregation::DIRECTION_BACKWARD ? m_owner : m_target); @@ -372,11 +384,11 @@ void QtGraphEdge::onHide() } } -void QtGraphEdge::focusIn() +void QtGraphEdge::coFocusIn() { - if (!m_isFocused) + if (!m_isCoFocused) { - m_isFocused = true; + m_isCoFocused = true; updateLine(); if (s_focusedEdge == this) @@ -419,11 +431,11 @@ void QtGraphEdge::focusIn() } } -void QtGraphEdge::focusOut() +void QtGraphEdge::coFocusOut() { - if (m_isFocused) + if (m_isCoFocused) { - m_isFocused = false; + m_isCoFocused = false; updateLine(); if (s_focusedEdge == this) @@ -468,11 +480,13 @@ void QtGraphEdge::mouseReleaseEvent(QGraphicsSceneMouseEvent* event) void QtGraphEdge::hoverEnterEvent(QGraphicsSceneHoverEvent* event) { + m_focusHandler->focusEdge(this); + if (m_useBezier) { if (s_focusedBezierEdge && s_focusedBezierEdge != this) { - s_focusedBezierEdge->focusOut(); + s_focusedBezierEdge->coFocusOut(); } s_focusedBezierEdge = this; @@ -482,25 +496,27 @@ void QtGraphEdge::hoverEnterEvent(QGraphicsSceneHoverEvent* event) if (getData() && !m_useBezier) { - MessageFocusIn(std::vector(1, getData()->getId()), TOOLTIP_ORIGIN_GRAPH).dispatch(); + MessageFocusIn({getData()->getId()}, TOOLTIP_ORIGIN_GRAPH).dispatch(); } else { - focusIn(); + coFocusIn(); } } void QtGraphEdge::hoverLeaveEvent(QGraphicsSceneHoverEvent* event) { + m_focusHandler->defocusEdge(this); + s_focusedBezierEdge = nullptr; if (getData() && !m_useBezier) { - MessageFocusOut(std::vector(1, getData()->getId())).dispatch(); + MessageFocusOut({getData()->getId()}).dispatch(); } else { - focusOut(); + coFocusOut(); } s_focusedEdge = nullptr; @@ -515,6 +531,16 @@ void QtGraphEdge::setDirection(TokenComponentAggregation::Direction direction) } } +bool QtGraphEdge::isHorizontal() const +{ + return m_isHorizontal; +} + +bool QtGraphEdge::isExpandable() const +{ + return !getData() || m_owner->isGroupNode() || m_target->isGroupNode(); +} + bool QtGraphEdge::isTrailEdge() const { return m_isTrailEdge; @@ -528,6 +554,11 @@ void QtGraphEdge::setIsTrailEdge(const std::vector& path, bool horizontal m_isHorizontal = horizontal; } +bool QtGraphEdge::isBezierEdge() const +{ + return m_useBezier; +} + void QtGraphEdge::setUseBezier(bool useBezier) { m_useBezier = useBezier; @@ -543,3 +574,13 @@ bool QtGraphEdge::isAmbiguous() const { return m_data && m_data->getComponent(); } + +QRectF QtGraphEdge::getBoundingRect() const +{ + if (m_child) + { + return m_child->sceneBoundingRect(); + } + + return QRectF(); +} diff --git a/src/lib_gui/qt/graphics/graph/QtGraphEdge.h b/src/lib_gui/qt/graphics/graph/QtGraphEdge.h index 02975b2c..c09b8671 100644 --- a/src/lib_gui/qt/graphics/graph/QtGraphEdge.h +++ b/src/lib_gui/qt/graphics/graph/QtGraphEdge.h @@ -11,6 +11,7 @@ #include "TokenComponentAggregation.h" class Edge; +class GraphFocusHandler; class QtGraphNode; class QtGraphEdge @@ -24,6 +25,7 @@ public: static void unfocusBezierEdge(); QtGraphEdge( + GraphFocusHandler* focusHandler, QtGraphNode* owner, QtGraphNode* target, const Edge* data, @@ -47,23 +49,32 @@ public: void setIsActive(bool isActive); void setIsFocused(bool isFocused); + void setIsCoFocused(bool isCoFocused); + + bool isFocusable() const; void onClick(); void onHide(); - void focusIn(); - void focusOut(); + void coFocusIn(); + void coFocusOut(); void setDirection(TokenComponentAggregation::Direction direction); + bool isHorizontal() const; + bool isExpandable() const; + bool isTrailEdge() const; void setIsTrailEdge(const std::vector& path, bool horizontal); + bool isBezierEdge() const; void setUseBezier(bool useBezier); void clearPath(); bool isAmbiguous() const; + QRectF getBoundingRect() const; + protected: virtual void mousePressEvent(QGraphicsSceneMouseEvent* event); virtual void mouseMoveEvent(QGraphicsSceneMouseEvent* event); @@ -80,29 +91,32 @@ private: // used to unfocus recent edge, because hover leave event is not always received for bezier edges static QtGraphEdge* s_focusedBezierEdge; + GraphFocusHandler* m_focusHandler = nullptr; + const Edge* m_data; - QtGraphNode* m_owner; - QtGraphNode* m_target; + QtGraphNode* m_owner = nullptr; + QtGraphNode* m_target = nullptr; - QGraphicsItem* m_child; + QGraphicsItem* m_child = nullptr; - bool m_isActive; - bool m_isFocused; + bool m_isActive = false; + bool m_isFocused = false; + bool m_isCoFocused = false; - bool m_isHorizontal; - size_t m_weight; + bool m_isHorizontal = false; + size_t m_weight = 0; TokenComponentAggregation::Direction m_direction; - bool m_isTrailEdge; + bool m_isTrailEdge = false; std::vector m_path; - bool m_useBezier; - bool m_isInteractive; + bool m_useBezier = false; + bool m_isInteractive = false; Vec2i m_mousePos; - bool m_mouseMoved; + bool m_mouseMoved = false; }; #endif // QT_GRAPH_EDGE_H diff --git a/src/lib_gui/qt/graphics/graph/QtGraphNode.cpp b/src/lib_gui/qt/graphics/graph/QtGraphNode.cpp index 9702920f..947b1c5f 100644 --- a/src/lib_gui/qt/graphics/graph/QtGraphNode.cpp +++ b/src/lib_gui/qt/graphics/graph/QtGraphNode.cpp @@ -7,8 +7,8 @@ #include #include +#include "GraphFocusHandler.h" #include "MessageCodeShowDefinition.h" -#include "MessageGraphNodeExpand.h" #include "MessageGraphNodeHide.h" #include "MessageGraphNodeMove.h" #include "QtDeviceScaledPixmap.h" @@ -40,7 +40,26 @@ void QtGraphNode::hideNode() this->hide(); } -QtGraphNode::QtGraphNode() +QtGraphNode* QtGraphNode::findNodeRecursive(const std::list& nodes, Id tokenId) +{ + for (QtGraphNode* node: nodes) + { + if (node->getTokenId() == tokenId) + { + return node; + } + + QtGraphNode* result = findNodeRecursive(node->getSubNodes(), tokenId); + if (result != nullptr) + { + return result; + } + } + + return nullptr; +} + +QtGraphNode::QtGraphNode(GraphFocusHandler* focusHandler): m_focusHandler(focusHandler) { this->setPen(QPen(Qt::transparent)); this->setCursor(Qt::PointingHandCursor); @@ -208,6 +227,11 @@ bool QtGraphNode::hasActiveChild() const return false; } +bool QtGraphNode::isFocusable() const +{ + return m_isInteractive && (isDataNode() || isGroupNode() || isBundleNode()); +} + std::wstring QtGraphNode::getName() const { return m_text->text().toStdWString(); @@ -234,28 +258,84 @@ void QtGraphNode::hoverEnter() } } +bool QtGraphNode::getIsFocused() const +{ + return m_isFocused; +} + +void QtGraphNode::setIsFocused(bool focused) +{ + if (m_isFocused != focused) + { + m_isFocused = focused; + + if (focused) + { + coFocusIn(); + } + else + { + coFocusOut(); + } + } +} + void QtGraphNode::focusIn() { - m_isHovering = true; + if (m_isInteractive && m_focusHandler) + { + m_focusHandler->focusNode(this); + } + else + { + coFocusIn(); + } +} + +void QtGraphNode::focusOut() +{ + if (m_isInteractive && m_focusHandler) + { + m_focusHandler->defocusNode(this); + } + else + { + coFocusOut(); + } +} + +void QtGraphNode::coFocusIn() +{ + if (m_isCoFocused) + { + return; + } + + m_isCoFocused = true; forEachEdge([](QtGraphEdge* edge) { if (edge->isTrailEdge()) { - edge->focusIn(); + edge->coFocusIn(); } }); updateStyle(); } -void QtGraphNode::focusOut() +void QtGraphNode::coFocusOut() { - m_isHovering = false; + if (!m_isCoFocused) + { + return; + } + + m_isCoFocused = false; forEachEdge([](QtGraphEdge* edge) { if (edge->isTrailEdge()) { - edge->focusOut(); + edge->coFocusOut(); } }); @@ -390,23 +470,23 @@ void QtGraphNode::onHide() } } -void QtGraphNode::onCollapseExpand() +Id QtGraphNode::onCollapseExpand() { for (auto subNode: getSubNodes()) { if (subNode->isExpandToggleNode()) { - MessageGraphNodeExpand( - getTokenId(), !dynamic_cast(subNode)->isExpanded()) - .dispatch(); - return; + subNode->onClick(); + return getTokenId(); } } if (getParent()) { - getParent()->onCollapseExpand(); + return getParent()->onCollapseExpand(); } + + return 0; } void QtGraphNode::onShowDefinition(bool inIDE) diff --git a/src/lib_gui/qt/graphics/graph/QtGraphNode.h b/src/lib_gui/qt/graphics/graph/QtGraphNode.h index 4e6fd4ed..9b6e51cc 100644 --- a/src/lib_gui/qt/graphics/graph/QtGraphNode.h +++ b/src/lib_gui/qt/graphics/graph/QtGraphNode.h @@ -9,6 +9,7 @@ #include "GraphViewStyle.h" +class GraphFocusHandler; class QFont; class QtGraphEdge; class QtRoundedRectItem; @@ -31,7 +32,9 @@ public slots: void hideNode(); public: - QtGraphNode(); + static QtGraphNode* findNodeRecursive(const std::list& nodes, Id tokenId); + + QtGraphNode(GraphFocusHandler* focusHandler = nullptr); virtual ~QtGraphNode(); QtGraphNode* getParent() const; @@ -66,6 +69,8 @@ public: void setMultipleActive(bool multipleActive); bool hasActiveChild() const; + bool isFocusable() const; + std::wstring getName() const; void setName(const std::wstring& name); @@ -73,8 +78,12 @@ public: void hoverEnter(); + bool getIsFocused() const; + void setIsFocused(bool focused); void focusIn(); void focusOut(); + void coFocusIn(); + void coFocusOut(); void showNodeRecursive(); @@ -98,7 +107,7 @@ public: virtual void onMiddleClick(); void onHide(); - void onCollapseExpand(); + Id onCollapseExpand(); void onShowDefinition(bool inIDE); virtual void moved(const Vec2i& oldPosition); @@ -134,9 +143,13 @@ protected: bool m_isActive = false; bool m_multipleActive = false; - bool m_isHovering = false; + bool m_isFocused = false; + bool m_isCoFocused = false; + bool m_isInteractive = false; private: + GraphFocusHandler* m_focusHandler; + std::list> m_components; // Name match diff --git a/src/lib_gui/qt/graphics/graph/QtGraphNodeBundle.cpp b/src/lib_gui/qt/graphics/graph/QtGraphNodeBundle.cpp index e2fe6657..6df15a49 100644 --- a/src/lib_gui/qt/graphics/graph/QtGraphNodeBundle.cpp +++ b/src/lib_gui/qt/graphics/graph/QtGraphNodeBundle.cpp @@ -3,14 +3,21 @@ #include #include -#include "MessageGraphNodeBundleSplit.h" - #include "GraphViewStyle.h" +#include "MessageGraphNodeBundleSplit.h" #include "QtCountCircleItem.h" -QtGraphNodeBundle::QtGraphNodeBundle(Id tokenId, size_t nodeCount, NodeType type, const std::wstring& name) - : QtGraphNode(), m_tokenId(tokenId), m_type(type) +QtGraphNodeBundle::QtGraphNodeBundle( + GraphFocusHandler* focusHandler, + Id tokenId, + size_t nodeCount, + NodeType type, + const std::wstring& name, + bool interactive) + : QtGraphNode(focusHandler), m_tokenId(tokenId), m_type(type) { + m_isInteractive = interactive; + this->setName(name); m_circle = new QtCountCircleItem(this); @@ -48,11 +55,12 @@ void QtGraphNodeBundle::updateStyle() GraphViewStyle::NodeStyle style; if (!m_type.isUnknownSymbol()) { - style = GraphViewStyle::getStyleForNodeType(m_type, true, false, m_isHovering, false, false); + style = GraphViewStyle::getStyleForNodeType( + m_type, true, false, m_isFocused, m_isCoFocused, false, false); } else { - style = GraphViewStyle::getStyleOfBundleNode(m_isHovering); + style = GraphViewStyle::getStyleOfBundleNode(m_isFocused); } setStyle(style); diff --git a/src/lib_gui/qt/graphics/graph/QtGraphNodeBundle.h b/src/lib_gui/qt/graphics/graph/QtGraphNodeBundle.h index d1487202..3b049598 100644 --- a/src/lib_gui/qt/graphics/graph/QtGraphNodeBundle.h +++ b/src/lib_gui/qt/graphics/graph/QtGraphNodeBundle.h @@ -10,7 +10,13 @@ class QtGraphNodeBundle: public QtGraphNode { Q_OBJECT public: - QtGraphNodeBundle(Id tokenId, size_t nodeCount, NodeType type, const std::wstring& name); + QtGraphNodeBundle( + GraphFocusHandler* focusHandler, + Id tokenId, + size_t nodeCount, + NodeType type, + const std::wstring& name, + bool interactive); virtual ~QtGraphNodeBundle(); // QtGraphNode implementation diff --git a/src/lib_gui/qt/graphics/graph/QtGraphNodeData.cpp b/src/lib_gui/qt/graphics/graph/QtGraphNodeData.cpp index 7b14111f..4ac12f4e 100644 --- a/src/lib_gui/qt/graphics/graph/QtGraphNodeData.cpp +++ b/src/lib_gui/qt/graphics/graph/QtGraphNodeData.cpp @@ -1,6 +1,7 @@ #include "QtGraphNodeData.h" #include "FilePath.h" +#include "GraphFocusHandler.h" #include "MessageActivateNodes.h" #include "MessageDeactivateEdge.h" #include "MessageFocusIn.h" @@ -12,12 +13,18 @@ #include "TokenComponentFilePath.h" QtGraphNodeData::QtGraphNodeData( - const Node* data, const std::wstring& name, bool childVisible, bool hasQualifier, bool isInteractive) - : m_data(data) + GraphFocusHandler* focusHandler, + const Node* data, + const std::wstring& name, + bool childVisible, + bool hasQualifier, + bool isInteractive) + : QtGraphNode(focusHandler) + , m_data(data) , m_childVisible(childVisible) , m_hasQualifier(hasQualifier) - , m_isInteractive(isInteractive) { + m_isInteractive = isInteractive; this->setAcceptHoverEvents(true); this->setName(name); } @@ -69,7 +76,13 @@ void QtGraphNodeData::onMiddleClick() void QtGraphNodeData::updateStyle() { GraphViewStyle::NodeStyle style = GraphViewStyle::getStyleForNodeType( - m_data->getType(), m_data->isExplicit(), m_isActive, m_isHovering, m_childVisible, m_hasQualifier); + m_data->getType(), + m_data->isExplicit(), + m_isActive, + m_isFocused, + m_isCoFocused, + m_childVisible, + m_hasQualifier); TokenComponentFilePath* component = m_data->getComponent(); if (component && !component->isComplete()) @@ -83,8 +96,14 @@ void QtGraphNodeData::updateStyle() void QtGraphNodeData::hoverEnterEvent(QGraphicsSceneHoverEvent* event) { - MessageFocusIn(std::vector(1, m_data->getId()), TOOLTIP_ORIGIN_GRAPH).dispatch(); + focusIn(); + if (m_isInteractive) + { + MessageFocusIn({m_data->getId()}, TOOLTIP_ORIGIN_GRAPH).dispatch(); + } + + // case for legend if (!m_isInteractive) { TooltipInfo info; @@ -108,5 +127,10 @@ void QtGraphNodeData::hoverEnterEvent(QGraphicsSceneHoverEvent* event) void QtGraphNodeData::hoverLeaveEvent(QGraphicsSceneHoverEvent* event) { - MessageFocusOut(std::vector(1, m_data->getId())).dispatch(); + focusOut(); + + if (m_isInteractive) + { + MessageFocusOut({m_data->getId()}).dispatch(); + } } diff --git a/src/lib_gui/qt/graphics/graph/QtGraphNodeData.h b/src/lib_gui/qt/graphics/graph/QtGraphNodeData.h index 84ace593..423ac0c9 100644 --- a/src/lib_gui/qt/graphics/graph/QtGraphNodeData.h +++ b/src/lib_gui/qt/graphics/graph/QtGraphNodeData.h @@ -10,6 +10,7 @@ class QtGraphNodeData: public QtGraphNode Q_OBJECT public: QtGraphNodeData( + GraphFocusHandler* focusHandler, const Node* data, const std::wstring& name, bool childVisible, @@ -37,7 +38,6 @@ private: const Node* m_data; bool m_childVisible; bool m_hasQualifier; - bool m_isInteractive; }; #endif // QT_GRAPH_NODE_DATA_H diff --git a/src/lib_gui/qt/graphics/graph/QtGraphNodeGroup.cpp b/src/lib_gui/qt/graphics/graph/QtGraphNodeGroup.cpp index cc062460..210dfc54 100644 --- a/src/lib_gui/qt/graphics/graph/QtGraphNodeGroup.cpp +++ b/src/lib_gui/qt/graphics/graph/QtGraphNodeGroup.cpp @@ -10,12 +10,13 @@ #include "MessageFocusIn.h" #include "MessageFocusOut.h" #include "MessageGraphNodeBundleSplit.h" - #include "QtRoundedRectItem.h" -QtGraphNodeGroup::QtGraphNodeGroup(Id tokenId, const std::wstring& name, GroupType type, bool interactive) - : m_tokenId(tokenId), m_type(type), m_interactive(interactive) +QtGraphNodeGroup::QtGraphNodeGroup( + GraphFocusHandler* focusHandler, Id tokenId, const std::wstring& name, GroupType type, bool interactive) + : QtGraphNode(focusHandler), m_tokenId(tokenId), m_type(type) { + m_isInteractive = interactive; if (interactive) { setAcceptHoverEvents(true); @@ -72,7 +73,7 @@ Id QtGraphNodeGroup::getTokenId() const void QtGraphNodeGroup::onClick() { - if (!m_interactive || !m_isHovering) + if (!m_isInteractive || !m_isFocused) { return; } @@ -89,7 +90,7 @@ void QtGraphNodeGroup::onClick() void QtGraphNodeGroup::updateStyle() { - GraphViewStyle::NodeStyle style = GraphViewStyle::getStyleOfGroupNode(m_type, m_isHovering); + GraphViewStyle::NodeStyle style = GraphViewStyle::getStyleOfGroupNode(m_type, m_isCoFocused); if (m_background) { @@ -97,6 +98,11 @@ void QtGraphNodeGroup::updateStyle() m_background->setPen(QPen(Qt::transparent)); } + if (m_isFocused) + { + style.color.border = GraphViewStyle::getFocusColor(); + } + setStyle(style); } @@ -116,29 +122,25 @@ void QtGraphNodeGroup::hoverLeaveEvent(QGraphicsSceneHoverEvent* event) { MessageFocusOut({m_tokenId}).dispatch(); } - else - { - focusOut(); - } + + focusOut(); } void QtGraphNodeGroup::hoverMoveEvent(QGraphicsSceneHoverEvent* event) { if (!m_background || m_background->contains(event->pos())) { - if (!m_isHovering) + if (!m_isCoFocused) { if (m_type == GroupType::FILE || m_type == GroupType::NAMESPACE) { MessageFocusIn({m_tokenId}, TOOLTIP_ORIGIN_GRAPH).dispatch(); } - else - { - focusIn(); - } + + focusIn(); } } - else if (m_isHovering) + else if (m_isCoFocused) { hoverLeaveEvent(nullptr); } diff --git a/src/lib_gui/qt/graphics/graph/QtGraphNodeGroup.h b/src/lib_gui/qt/graphics/graph/QtGraphNodeGroup.h index 47775aa8..e841fb8c 100644 --- a/src/lib_gui/qt/graphics/graph/QtGraphNodeGroup.h +++ b/src/lib_gui/qt/graphics/graph/QtGraphNodeGroup.h @@ -11,7 +11,12 @@ class QtGraphNodeGroup: public QtGraphNode { Q_OBJECT public: - QtGraphNodeGroup(Id tokenId, const std::wstring& name, GroupType type, bool interactive); + QtGraphNodeGroup( + GraphFocusHandler* focusHandler, + Id tokenId, + const std::wstring& name, + GroupType type, + bool interactive); virtual ~QtGraphNodeGroup(); // QtGraphNode implementation @@ -30,7 +35,6 @@ protected: private: Id m_tokenId; GroupType m_type; - const bool m_interactive; QGraphicsPolygonItem* m_background = nullptr; mutable QPainterPath m_path; diff --git a/src/lib_gui/qt/view/QtCodeView.cpp b/src/lib_gui/qt/view/QtCodeView.cpp index 95778d4e..fc19d104 100644 --- a/src/lib_gui/qt/view/QtCodeView.cpp +++ b/src/lib_gui/qt/view/QtCodeView.cpp @@ -14,6 +14,9 @@ QtCodeView::QtCodeView(ViewLayout* viewLayout): CodeView(viewLayout) { m_widget = new QtCodeNavigator(); + + m_widget->connect(m_widget, &QtCodeNavigator::focusIn, [this](){ setNavigationFocus(true); }); + m_widget->connect(m_widget, &QtCodeNavigator::focusOut, [this](){ setNavigationFocus(false); }); } void QtCodeView::createWidgetWrapper() @@ -106,8 +109,8 @@ void QtCodeView::showSnippets( } m_widget->updateFiles(); - - m_widget->scrollTo(scrollParams, !params.clearSnippets); + m_widget->scrollTo(scrollParams, !params.clearSnippets, !params.locationIdToFocus); + m_widget->focusInitialLocation(params.locationIdToFocus); }); } @@ -136,7 +139,8 @@ void QtCodeView::showSingleFile( } m_widget->updateFiles(); - m_widget->scrollTo(scrollParams, animatedScroll); + m_widget->scrollTo(scrollParams, animatedScroll, !params.locationIdToFocus); + m_widget->focusInitialLocation(params.locationIdToFocus); } else { @@ -165,22 +169,24 @@ void QtCodeView::updateSourceLocations(const std::vector& files) m_widget->updateSourceLocations(*file.fileParams.get()); } } + + m_widget->focusInitialLocation(0); }); } void QtCodeView::scrollTo(const CodeScrollParams& params, bool animated) { - m_onQtThread([=]() { m_widget->scrollTo(params, animated); }); + m_onQtThread([=]() { m_widget->scrollTo(params, animated, true); }); } -void QtCodeView::focusTokenIds(const std::vector& focusedTokenIds) +void QtCodeView::coFocusTokenIds(const std::vector& coFocusedTokenIds) { - m_onQtThread([=]() { m_widget->focusTokenIds(focusedTokenIds); }); + m_onQtThread([=]() { m_widget->coFocusTokenIds(coFocusedTokenIds); }); } -void QtCodeView::defocusTokenIds() +void QtCodeView::deCoFocusTokenIds() { - m_onQtThread([=]() { m_widget->defocusTokenIds(); }); + m_onQtThread([=]() { m_widget->deCoFocusTokenIds(); }); } bool QtCodeView::isInListMode() const @@ -198,6 +204,27 @@ bool QtCodeView::hasSingleFileCached(const FilePath& filePath) const return m_widget->hasSingleFileCached(filePath); } +void QtCodeView::setNavigationFocus(bool focus) +{ + if (m_hasFocus == focus) + { + return; + } + + m_hasFocus = focus; + + m_onQtThread([this, focus]() { + m_widget->blockSignals(true); + m_widget->setNavigationFocus(focus); + m_widget->blockSignals(false); + }); +} + +bool QtCodeView::hasNavigationFocus() const +{ + return m_hasFocus; +} + void QtCodeView::setNavigationState(const CodeParams& params) { m_widget->setActiveTokenIds(params.activeTokenIds); diff --git a/src/lib_gui/qt/view/QtCodeView.h b/src/lib_gui/qt/view/QtCodeView.h index 34135f8b..e68a86ad 100644 --- a/src/lib_gui/qt/view/QtCodeView.h +++ b/src/lib_gui/qt/view/QtCodeView.h @@ -42,14 +42,17 @@ public: bool showsErrors() const override; - void focusTokenIds(const std::vector& focusedTokenIds) override; - void defocusTokenIds() override; + void coFocusTokenIds(const std::vector& coFocusedTokenIds) override; + void deCoFocusTokenIds() override; bool isInListMode() const override; void setMode(bool listMode) override; bool hasSingleFileCached(const FilePath& filePath) const override; + void setNavigationFocus(bool focus) override; + bool hasNavigationFocus() const override; + private: void setNavigationState(const CodeParams& params); @@ -59,6 +62,8 @@ private: QtThreadedLambdaFunctor m_onQtThread; QtCodeNavigator* m_widget; + + bool m_hasFocus = false; }; #endif // QT_CODE_VIEW_H diff --git a/src/lib_gui/qt/view/QtCompositeView.cpp b/src/lib_gui/qt/view/QtCompositeView.cpp index ea813d6c..fb533c5c 100644 --- a/src/lib_gui/qt/view/QtCompositeView.cpp +++ b/src/lib_gui/qt/view/QtCompositeView.cpp @@ -7,25 +7,40 @@ #include "utilityQt.h" QtCompositeView::QtCompositeView( - ViewLayout* viewLayout, CompositeDirection direction, const std::string& name) - : CompositeView(viewLayout, direction, name) + ViewLayout* viewLayout, CompositeDirection direction, const std::string& name, Id tabId) + : CompositeView(viewLayout, direction, name, tabId) { - QBoxLayout* layout; + QBoxLayout* topLayout = new QVBoxLayout(); + topLayout->setSpacing(0); + topLayout->setContentsMargins(0, 0, 0, 0); + + const size_t indicatorHeight = 3; + + { + m_focusIndicator = new QWidget(); + m_focusIndicator->setObjectName(QStringLiteral("focus_indicator")); + m_focusIndicator->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Fixed); + m_focusIndicator->setFixedHeight(indicatorHeight); + topLayout->addWidget(m_focusIndicator); + } + if (getDirection() == CompositeView::DIRECTION_HORIZONTAL) { - layout = new QHBoxLayout(); + m_layout = new QHBoxLayout(); } else { - layout = new QVBoxLayout(); + m_layout = new QVBoxLayout(); } - layout->setSpacing(5); - layout->setContentsMargins(8, 8, 8, 8); - layout->setAlignment(Qt::AlignTop); + m_layout->setSpacing(5); + m_layout->setContentsMargins(8, 8 - indicatorHeight, 8, 7); + m_layout->setAlignment(Qt::AlignTop); + + topLayout->addLayout(m_layout); m_widget = new QWidget(); - m_widget->setLayout(layout); + m_widget->setLayout(topLayout); refreshView(); } @@ -41,9 +56,19 @@ void QtCompositeView::refreshView() utility::setWidgetBackgroundColor( m_widget, ColorScheme::getInstance()->getColor("search/background")); }); + + showFocusIndicator(false); } void QtCompositeView::addViewWidget(View* view) { - m_widget->layout()->addWidget(QtViewWidgetWrapper::getWidgetOfView(view)); + m_layout->addWidget(QtViewWidgetWrapper::getWidgetOfView(view)); +} + +void QtCompositeView::showFocusIndicator(bool focus) +{ + m_onQtThread([=]() { + const std::string& colorName = focus ? "window/focus" : "search/background"; + utility::setWidgetBackgroundColor(m_focusIndicator, ColorScheme::getInstance()->getColor(colorName)); + }); } diff --git a/src/lib_gui/qt/view/QtCompositeView.h b/src/lib_gui/qt/view/QtCompositeView.h index ddc9ddf3..6575c726 100644 --- a/src/lib_gui/qt/view/QtCompositeView.h +++ b/src/lib_gui/qt/view/QtCompositeView.h @@ -1,15 +1,16 @@ #ifndef QT_COMPOSITE_VIEW #define QT_COMPOSITE_VIEW -#include - #include "CompositeView.h" #include "QtThreadedFunctor.h" +class QBoxLayout; +class QWidget; + class QtCompositeView: public CompositeView { public: - QtCompositeView(ViewLayout* viewLayout, CompositeDirection direction, const std::string& name); + QtCompositeView(ViewLayout* viewLayout, CompositeDirection direction, const std::string& name, Id tabId); ~QtCompositeView() = default; // View implementation @@ -18,10 +19,13 @@ public: // CompositeView implementation void addViewWidget(View* view) override; + void showFocusIndicator(bool focus) override; private: QtThreadedLambdaFunctor m_onQtThread; + QWidget* m_focusIndicator; QWidget* m_widget; + QBoxLayout* m_layout; }; #endif // QT_COMPOSITE_VIEW diff --git a/src/lib_gui/qt/view/QtCustomTrailView.cpp b/src/lib_gui/qt/view/QtCustomTrailView.cpp index fd1b9ce8..71bf2ca5 100644 --- a/src/lib_gui/qt/view/QtCustomTrailView.cpp +++ b/src/lib_gui/qt/view/QtCustomTrailView.cpp @@ -215,8 +215,7 @@ QtCustomTrailView::QtCustomTrailView(ViewLayout* viewLayout) for (NodeKind t: nodeKinds) { nodeFilters.push_back(QString::fromStdString(getReadableNodeKindString(t))); - nodeColors.push_back( - QColor(scheme->getNodeTypeColor(NodeType(t), "fill", ColorScheme::FOCUS).c_str())); + nodeColors.push_back(QColor(scheme->getNodeTypeColor(NodeType(t), "fill", true).c_str())); } QVBoxLayout* filterLayout = addFilters( @@ -253,7 +252,7 @@ QtCustomTrailView::QtCustomTrailView(ViewLayout* viewLayout) for (Edge::EdgeType t: edgeTypes) { edgeFilters.push_back(QString::fromStdWString(Edge::getReadableTypeString(t))); - edgeColors.push_back(QColor(scheme->getEdgeTypeColor(t, ColorScheme::FOCUS).c_str())); + edgeColors.push_back(QColor(scheme->getEdgeTypeColor(t).c_str())); } QVBoxLayout* filterLayout = addFilters( diff --git a/src/lib_gui/qt/view/QtGraphView.cpp b/src/lib_gui/qt/view/QtGraphView.cpp index 988bf382..f7556098 100644 --- a/src/lib_gui/qt/view/QtGraphView.cpp +++ b/src/lib_gui/qt/view/QtGraphView.cpp @@ -41,6 +41,7 @@ QtGraphView::QtGraphView(ViewLayout* viewLayout) : GraphView(viewLayout) + , m_focusHandler(this) , m_centerActiveNode(false) , m_scrollToTop(false) , m_restoreScroll(false) @@ -56,7 +57,7 @@ QtGraphView::QtGraphView(ViewLayout* viewLayout) widget->setLayout(layout); QGraphicsScene* scene = new QGraphicsScene(widget); - QtGraphicsView* view = new QtGraphicsView(widget); + QtGraphicsView* view = new QtGraphicsView(&m_focusHandler, widget); view->setScene(scene); view->setDragMode(QGraphicsView::ScrollHandDrag); view->setRenderHints(QPainter::Antialiasing | QPainter::SmoothPixmapTransform); @@ -65,8 +66,9 @@ QtGraphView::QtGraphView(ViewLayout* viewLayout) widget->layout()->addWidget(view); connect(view, &QtGraphicsView::emptySpaceClicked, this, &QtGraphView::clickedInEmptySpace); - connect(view, &QtGraphicsView::characterKeyPressed, this, &QtGraphView::pressedCharacterKey); connect(view, &QtGraphicsView::resized, this, &QtGraphView::resized); + connect(view, &QtGraphicsView::focusIn, [this](){ setNavigationFocus(true); }); + connect(view, &QtGraphicsView::focusOut, [this](){ setNavigationFocus(false); }); m_scrollSpeedChangeListenerHorizontal.setScrollBar(view->horizontalScrollBar()); m_scrollSpeedChangeListenerVertical.setScrollBar(view->verticalScrollBar()); @@ -345,6 +347,7 @@ void QtGraphView::rebuildGraph( activeNodeCount += nodes[i]->getActiveSubNodeCount(); } + Id oldActiveTokenId = m_oldActiveNode ? m_oldActiveNode->getTokenId() : 0; m_nodes.clear(); m_activeNodes.clear(); m_oldActiveNode = nullptr; @@ -360,6 +363,12 @@ void QtGraphView::rebuildGraph( } } + if (m_activeNodes.size() == 1) + { + m_oldActiveNode = m_activeNodes.front(); + } + + Id newActiveTokenId = m_oldActiveNode ? m_oldActiveNode->getTokenId() : 0; // move graph to center QPointF center = itemsBoundingRect(m_nodes).center(); @@ -400,6 +409,16 @@ void QtGraphView::rebuildGraph( } } + // focus previously focused node + if (params.tokenIdToFocus) + { + m_focusHandler.focusTokenId(m_nodes, m_edges, params.tokenIdToFocus); + } + else + { + m_focusHandler.refocusNode(m_nodes, oldActiveTokenId, newActiveTokenId); + } + m_centerActiveNode = params.centerActiveNode; m_scrollToTop = params.scrollToTop; m_isIndexedList = params.isIndexedList; @@ -419,6 +438,8 @@ void QtGraphView::rebuildGraph( void QtGraphView::clear() { m_onQtThread([this]() { + m_focusHandler.clear(); + m_oldActiveNode = nullptr; m_activeNodes.clear(); @@ -447,15 +468,15 @@ void QtGraphView::clear() }); } -void QtGraphView::focusTokenIds(const std::vector& focusedTokenIds) +void QtGraphView::coFocusTokenIds(const std::vector& focusedTokenIds) { m_onQtThread([=]() { for (const Id& tokenId: focusedTokenIds) { - QtGraphNode* node = findNodeRecursive(m_oldNodes, tokenId); - if (node) + QtGraphNode* node = QtGraphNode::findNodeRecursive(m_oldNodes, tokenId); + if (node && !node->getIsFocused()) { - node->focusIn(); + node->coFocusIn(); continue; } @@ -463,7 +484,7 @@ void QtGraphView::focusTokenIds(const std::vector& focusedTokenIds) { if (edge->getData() && edge->getData()->getId() == tokenId) { - edge->focusIn(); + edge->coFocusIn(); break; } } @@ -471,15 +492,15 @@ void QtGraphView::focusTokenIds(const std::vector& focusedTokenIds) }); } -void QtGraphView::defocusTokenIds(const std::vector& defocusedTokenIds) +void QtGraphView::deCoFocusTokenIds(const std::vector& defocusedTokenIds) { m_onQtThread([=]() { for (const Id& tokenId: defocusedTokenIds) { - QtGraphNode* node = findNodeRecursive(m_oldNodes, tokenId); - if (node && (node->isDataNode() || node->isGroupNode())) + QtGraphNode* node = QtGraphNode::findNodeRecursive(m_oldNodes, tokenId); + if (node && !node->getIsFocused()) { - node->focusOut(); + node->coFocusOut(); continue; } @@ -487,7 +508,7 @@ void QtGraphView::defocusTokenIds(const std::vector& defocusedTokenIds) { if (edge->getData() && edge->getData()->getId() == tokenId) { - edge->focusOut(); + edge->coFocusOut(); break; } } @@ -543,7 +564,6 @@ void QtGraphView::activateEdge(Id edgeId) for (QtGraphEdge* edge: m_oldEdges) { edge->setIsActive(false); - edge->setIsFocused(false); } for (QtGraphEdge* edge: m_oldEdges) @@ -557,6 +577,87 @@ void QtGraphView::activateEdge(Id edgeId) }); } +void QtGraphView::setNavigationFocus(bool focus) +{ + if (m_hasFocus == focus) + { + return; + } + + m_hasFocus = focus; + + m_onQtThread([this, focus]() { + focusView(focus); + + if (focus) + { + m_focusHandler.focus(); + } + else + { + m_focusHandler.defocus(); + } + }); +} + +bool QtGraphView::hasNavigationFocus() const +{ + return m_hasFocus; +} + +void QtGraphView::focusView(bool focusIn) +{ + QtGraphicsView* view = getView(); + + view->blockSignals(true); + + if (focusIn) + { + view->setFocus(); + } + else + { + view->clearFocus(); + } + + view->blockSignals(false); +} + +const std::list& QtGraphView::getGraphNodes() const +{ + return m_oldNodes; +} + +const std::list& QtGraphView::getGraphEdges() const +{ + return m_oldEdges; +} + +QtGraphNode* QtGraphView::getActiveNode() const +{ + return m_oldActiveNode; +} + +void QtGraphView::ensureNodeVisible(QtGraphNode* node) +{ + QtGraphicsView* view = getView(); + + Vec4i r = node->getBoundingRect(); + QRectF rect(r.x(), r.y(), r.z() - r.x(), r.w() - r.y()); + + if (rect.width() > view->width() - 100) + { + rect.setWidth(view->width() - 100); + } + + if (rect.height() > view->height() - 100) + { + rect.setHeight(view->height() - 100); + } + + view->ensureVisibleAnimated(rect, 100, 100); +} + void QtGraphView::updateScrollBars() { QGraphicsView* view = getView(); @@ -615,8 +716,6 @@ void QtGraphView::clickedInEmptySpace() { activeEdges.push_back(edge); } - - edge->setIsFocused(false); } if (m_graph && m_graph->getTrailMode() != Graph::TRAIL_NONE) @@ -632,60 +731,6 @@ void QtGraphView::clickedInEmptySpace() } } -void QtGraphView::pressedCharacterKey(QChar c) -{ - if (!m_isIndexedList) - { - return; - } - - const QtGraphNode* node = nullptr; - bool hasTextNodes = false; - - std::vector nodes(m_oldNodes.begin(), m_oldNodes.end()); - - size_t i = 0; - while (i < nodes.size()) - { - QtGraphNode* n = nodes[i++]; - if (n->isGroupNode()) - { - nodes.insert(nodes.end(), n->getSubNodes().begin(), n->getSubNodes().end()); - } - else if (n->isTextNode() && n->getName().size()) - { - hasTextNodes = true; - QChar start(n->getName()[0]); - if (start.toLower() >= c.toLower()) - { - node = n; - break; - } - } - } - - if (!hasTextNodes) - { - return; - } - - QtGraphicsView* view = getView(); - - if (!node) - { - view->ensureVisibleAnimated( - QRectF(0, view->scene()->height() - 5, view->scene()->width(), 5), 100, 100); - } - else - { - Vec2i pos = node->getPosition(); - Vec2i size = node->getSize(); - - view->ensureVisibleAnimated( - QRectF(pos.x, pos.y, size.x, size.y + getViewSize().y / 3 * 2), 100, 100); - } -} - void QtGraphView::scrolled(int) { QGraphicsView* view = getView(); @@ -914,13 +959,14 @@ void QtGraphView::switchToNewGraphData() updateScrollBars(); } - // Manually hover the item below the mouse cursor. QtGraphicsView* view = getView(); - QtGraphNode* node = view->getNodeAtCursorPosition(); - if (node) - { - node->hoverEnter(); - } + + // // Manually hover the item below the mouse cursor. + // QtGraphNode* node = view->getNodeAtCursorPosition(); + // if (node) + // { + // node->hoverEnter(); + // } if (m_activeNodes.size()) { @@ -928,14 +974,14 @@ void QtGraphView::switchToNewGraphData() { centerNode(m_activeNodes.front()); } - - if (m_activeNodes.size() == 1) - { - m_oldActiveNode = m_activeNodes.front(); - } m_activeNodes.clear(); } + if (hasNavigationFocus()) + { + m_focusHandler.focusInitialNode(); + } + // Repaint to make sure all artifacts are removed view->update(); @@ -966,25 +1012,6 @@ void QtGraphView::doResize() getView()->setSceneRect(getSceneRect(m_oldNodes)); } -QtGraphNode* QtGraphView::findNodeRecursive(const std::list& nodes, Id tokenId) -{ - for (QtGraphNode* node: nodes) - { - if (node->getTokenId() == tokenId) - { - return node; - } - - QtGraphNode* result = findNodeRecursive(node->getSubNodes(), tokenId); - if (result != nullptr) - { - return result; - } - } - - return nullptr; -} - QtGraphNode* QtGraphView::createNodeRecursive( QGraphicsView* view, QtGraphNode* parentNode, @@ -1001,7 +1028,12 @@ QtGraphNode* QtGraphView::createNodeRecursive( if (node->isGraphNode()) { newNode = new QtGraphNodeData( - node->data, node->name, node->childVisible, node->getQualifierNode() != nullptr, interactive); + &m_focusHandler, + node->data, + node->name, + node->childVisible, + node->getQualifierNode() != nullptr, + interactive); } else if (node->isAccessNode()) { @@ -1014,7 +1046,12 @@ QtGraphNode* QtGraphView::createNodeRecursive( else if (node->isBundleNode()) { newNode = new QtGraphNodeBundle( - node->tokenId, node->getBundledNodeCount(), node->bundledNodeType, node->name); + &m_focusHandler, + node->tokenId, + node->getBundledNodeCount(), + node->bundledNodeType, + node->name, + interactive); } else if (node->isQualifierNode()) { @@ -1026,7 +1063,12 @@ QtGraphNode* QtGraphView::createNodeRecursive( } else if (node->isGroupNode()) { - newNode = new QtGraphNodeGroup(node->tokenId, node->name, node->groupType, node->interactive); + newNode = new QtGraphNodeGroup( + &m_focusHandler, + node->tokenId, + node->name, + node->groupType, + node->interactive && interactive); } else { @@ -1090,12 +1132,13 @@ QtGraphEdge* QtGraphView::createEdge( return nullptr; } - QtGraphNode* owner = findNodeRecursive(m_nodes, edge->ownerId); - QtGraphNode* target = findNodeRecursive(m_nodes, edge->targetId); + QtGraphNode* owner = QtGraphNode::findNodeRecursive(m_nodes, edge->ownerId); + QtGraphNode* target = QtGraphNode::findNodeRecursive(m_nodes, edge->targetId); if (owner != nullptr && target != nullptr) { QtGraphEdge* qtEdge = new QtGraphEdge( + &m_focusHandler, owner, target, edge->data, diff --git a/src/lib_gui/qt/view/QtGraphView.h b/src/lib_gui/qt/view/QtGraphView.h index 62ef59ea..c63c3eb0 100644 --- a/src/lib_gui/qt/view/QtGraphView.h +++ b/src/lib_gui/qt/view/QtGraphView.h @@ -7,6 +7,7 @@ #include #include "Graph.h" +#include "GraphFocusHandler.h" #include "GraphView.h" #include "QtScrollSpeedChangeListener.h" #include "QtThreadedFunctor.h" @@ -28,6 +29,7 @@ class QtSelfRefreshIconButton; class QtGraphView : public QObject , public GraphView + , public GraphFocusClient { Q_OBJECT @@ -54,8 +56,8 @@ public: const GraphParams params) override; void clear() override; - void focusTokenIds(const std::vector& focusedTokenIds) override; - void defocusTokenIds(const std::vector& defocusedTokenIds) override; + void coFocusTokenIds(const std::vector& focusedTokenIds) override; + void deCoFocusTokenIds(const std::vector& defocusedTokenIds) override; void resizeView() override; @@ -66,11 +68,23 @@ public: void activateEdge(Id edgeId) override; + void setNavigationFocus(bool focus) override; + bool hasNavigationFocus() const override; + + // GraphFocusClient implementation + void focusView(bool focusIn) override; + + const std::list& getGraphNodes() const override; + const std::list& getGraphEdges() const override; + + QtGraphNode* getActiveNode() const override; + + void ensureNodeVisible(QtGraphNode* node) override; + private slots: void updateScrollBars(); void finishedTransition(); void clickedInEmptySpace(); - void pressedCharacterKey(QChar c); void scrolled(int); void resized(); @@ -100,8 +114,6 @@ private: void doResize(); - QtGraphNode* findNodeRecursive(const std::list& nodes, Id tokenId); - QtGraphNode* createNodeRecursive( QGraphicsView* view, QtGraphNode* parentNode, @@ -133,6 +145,9 @@ private: void createTransition(); + GraphFocusHandler m_focusHandler; + bool m_hasFocus = false; + QtThreadedLambdaFunctor m_onQtThread; std::shared_ptr m_graph; diff --git a/src/lib_gui/qt/view/QtMainView.cpp b/src/lib_gui/qt/view/QtMainView.cpp index 3b72b686..d2c72a77 100644 --- a/src/lib_gui/qt/view/QtMainView.cpp +++ b/src/lib_gui/qt/view/QtMainView.cpp @@ -3,11 +3,12 @@ #include "MessageRefreshUIState.h" #include "QtMainWindow.h" #include "QtViewWidgetWrapper.h" +#include "utilityApp.h" QtMainView::QtMainView(const ViewFactory* viewFactory, StorageAccess* storageAccess) : MainView(viewFactory, storageAccess) { - m_window = std::make_shared(); + m_window = new QtMainWindow(); m_window->show(); } @@ -15,11 +16,12 @@ QtMainView::~QtMainView() { // clear components to avoid double deletion of views when destroying m_window m_componentManager.clear(); + m_window->deleteLater(); } QtMainWindow* QtMainView::getMainWindow() const { - return m_window.get(); + return m_window; } void QtMainView::addView(View* view) @@ -170,3 +172,15 @@ void QtMainView::handleMessage(MessageProjectNew* message) m_onQtThread([=]() { m_window->newProjectFromCDB(cdbPath); }); } + +void QtMainView::handleMessage(MessageWindowChanged* message) +{ + // Fixes an issue where newly added QtWidgets don't fully respond to focus events on macOS + if (utility::getOsType() == OS_MAC) + { + m_onQtThread([=]() { + m_window->hide(); + m_window->show(); + }); + } +} diff --git a/src/lib_gui/qt/view/QtMainView.h b/src/lib_gui/qt/view/QtMainView.h index 5206ec76..dd4a224a 100644 --- a/src/lib_gui/qt/view/QtMainView.h +++ b/src/lib_gui/qt/view/QtMainView.h @@ -4,22 +4,22 @@ #include #include -#include - #include "MainView.h" -#include "QtThreadedFunctor.h" - #include "MessageListener.h" #include "MessageProjectEdit.h" #include "MessageProjectNew.h" +#include "MessageWindowChanged.h" +#include "QtThreadedFunctor.h" class QtMainWindow; +class QStatusBar; class View; class QtMainView : public MainView , public MessageListener , public MessageListener + , public MessageListener { public: QtMainView(const ViewFactory* viewFactory, StorageAccess* storageAccess); @@ -68,8 +68,9 @@ public: private: void handleMessage(MessageProjectEdit* message) override; void handleMessage(MessageProjectNew* message) override; + void handleMessage(MessageWindowChanged* message) override; - std::shared_ptr m_window; + QtMainWindow* m_window; std::vector m_views; QtThreadedLambdaFunctor m_onQtThread; diff --git a/src/lib_gui/qt/view/QtViewFactory.cpp b/src/lib_gui/qt/view/QtViewFactory.cpp index 57cc1c1d..e5a8d92a 100644 --- a/src/lib_gui/qt/view/QtViewFactory.cpp +++ b/src/lib_gui/qt/view/QtViewFactory.cpp @@ -28,9 +28,9 @@ std::shared_ptr QtViewFactory::createMainView(StorageAccess* storageAc } std::shared_ptr QtViewFactory::createCompositeView( - ViewLayout* viewLayout, CompositeView::CompositeDirection direction, const std::string& name) const + ViewLayout* viewLayout, CompositeView::CompositeDirection direction, const std::string& name, const Id tabId) const { - return View::createAndAddToLayout(viewLayout, direction, name); + return View::createAndAddToLayout(viewLayout, direction, name, tabId); } std::shared_ptr QtViewFactory::createTabbedView( diff --git a/src/lib_gui/qt/view/QtViewFactory.h b/src/lib_gui/qt/view/QtViewFactory.h index 29efca09..9921261b 100644 --- a/src/lib_gui/qt/view/QtViewFactory.h +++ b/src/lib_gui/qt/view/QtViewFactory.h @@ -13,7 +13,8 @@ public: std::shared_ptr createCompositeView( ViewLayout* viewLayout, CompositeView::CompositeDirection direction, - const std::string& name) const override; + const std::string& name, + const Id tabId) const override; std::shared_ptr createTabbedView( ViewLayout* viewLayout, const std::string& name) const override; diff --git a/src/lib_gui/qt/window/QtKeyboardShortcuts.cpp b/src/lib_gui/qt/window/QtKeyboardShortcuts.cpp index 2964cadf..a294ad5a 100644 --- a/src/lib_gui/qt/window/QtKeyboardShortcuts.cpp +++ b/src/lib_gui/qt/window/QtKeyboardShortcuts.cpp @@ -78,6 +78,36 @@ void QtKeyboardShortcuts::windowReady() setPreviousVisible(false); } +QtKeyboardShortcuts::Shortcut::Shortcut(const QString& name, const QString& shortcut) + : name(name), shortcut(shortcut) +{ +} + +QtKeyboardShortcuts::Shortcut QtKeyboardShortcuts::Shortcut::defaultOrMac( + const QString& name, const QString& defaultShortcut, const QString& macShortcut) +{ +#if defined(Q_OS_MAC) + return {name, macShortcut}; +#else + return {name, defaultShortcut}; +#endif +} + +QtKeyboardShortcuts::Shortcut QtKeyboardShortcuts::Shortcut::winMacOrLinux( + const QString& name, + const QString& winShortcut, + const QString& macShortcut, + const QString& linuxShortcut) +{ +#if defined(Q_OS_WIN32) + return {name, winShortcut}; +#elif defined(Q_OS_MAC) + return {name, macShortcut}; +#else + return {name, linuxShortcut}; +#endif +} + QtShortcutTable* QtKeyboardShortcuts::createTableWidget(const std::string& objectName) { QtShortcutTable* table = new QtShortcutTable(this); @@ -104,76 +134,81 @@ QtShortcutTable* QtKeyboardShortcuts::createTableWidget(const std::string& objec return table; } +void QtKeyboardShortcuts::addShortcuts(QtShortcutTable* table, const std::vector& shortcuts) const +{ + table->setRowCount(static_cast(shortcuts.size())); + + for (size_t i = 0; i < shortcuts.size(); ++i) + { + table->setItem(static_cast(i), 0, new QTableWidgetItem(shortcuts[i].name)); + table->setItem(static_cast(i), 1, new QTableWidgetItem(shortcuts[i].shortcut)); + } + + table->updateSize(); +} + QTableWidget* QtKeyboardShortcuts::createGenerelShortcutsTable() { QtShortcutTable* table = createTableWidget("table_general"); - table->setRowCount(17); - - table->setItem(0, 0, new QTableWidgetItem(QStringLiteral("Larger Font"))); - table->setItem(1, 0, new QTableWidgetItem(QStringLiteral("Smaller Font"))); - table->setItem(2, 0, new QTableWidgetItem(QStringLiteral("Reset Font Size"))); - table->setItem(3, 0, new QTableWidgetItem(QStringLiteral("Back"))); - table->setItem(4, 0, new QTableWidgetItem(QStringLiteral("Forward"))); - table->setItem(5, 0, new QTableWidgetItem(QStringLiteral("Refresh"))); - table->setItem(6, 0, new QTableWidgetItem(QStringLiteral("Full Refresh"))); - table->setItem(7, 0, new QTableWidgetItem(QStringLiteral("Find Symbol"))); - table->setItem(8, 0, new QTableWidgetItem(QStringLiteral("Find Text"))); - table->setItem(9, 0, new QTableWidgetItem(QStringLiteral("Find On-Screen"))); - table->setItem(10, 0, new QTableWidgetItem(QStringLiteral("New Project"))); - table->setItem(11, 0, new QTableWidgetItem(QStringLiteral("Open Project"))); - table->setItem(12, 0, new QTableWidgetItem(QStringLiteral("Close Window"))); - table->setItem(13, 0, new QTableWidgetItem(QStringLiteral("Hide Window"))); - table->setItem(14, 0, new QTableWidgetItem(QStringLiteral("To Overview"))); - table->setItem(15, 0, new QTableWidgetItem(QStringLiteral("Preferences"))); - table->setItem(16, 0, new QTableWidgetItem(QStringLiteral("Bookmark Active Symbol"))); - table->setItem(17, 0, new QTableWidgetItem(QStringLiteral("Bookmark Manager"))); - -#if defined(Q_OS_MAC) - table->setItem(0, 1, new QTableWidgetItem("Cmd + +")); - table->setItem(1, 1, new QTableWidgetItem("Cmd + -")); - table->setItem(2, 1, new QTableWidgetItem("Cmd + 0")); - table->setItem(3, 1, new QTableWidgetItem("Cmd + Z | Backspace")); - table->setItem(4, 1, new QTableWidgetItem("Cmd + Shift + Z")); - table->setItem(5, 1, new QTableWidgetItem("Cmd + R")); - table->setItem(6, 1, new QTableWidgetItem("Cmd + Shift + R")); - table->setItem(7, 1, new QTableWidgetItem("Cmd + F")); - table->setItem(8, 1, new QTableWidgetItem("Cmd + Shift + F")); - table->setItem(9, 1, new QTableWidgetItem("Cmd + D | /")); - table->setItem(10, 1, new QTableWidgetItem("Cmd + N")); - table->setItem(11, 1, new QTableWidgetItem("Cmd + O")); - table->setItem(12, 1, new QTableWidgetItem("Cmd + W")); - table->setItem(13, 1, new QTableWidgetItem("Cmd + H")); - table->setItem(14, 1, new QTableWidgetItem("Cmd + Home | Cmd + Up")); - table->setItem(15, 1, new QTableWidgetItem("Cmd + ,")); - table->setItem(16, 1, new QTableWidgetItem("Cmd + S")); - table->setItem(17, 1, new QTableWidgetItem("Cmd + B")); -#else - table->setItem(0, 1, new QTableWidgetItem(QStringLiteral("Ctrl + +"))); - table->setItem(1, 1, new QTableWidgetItem(QStringLiteral("Ctrl + -"))); - table->setItem(2, 1, new QTableWidgetItem(QStringLiteral("Ctrl + 0"))); - table->setItem(3, 1, new QTableWidgetItem(QStringLiteral("Ctrl + Z | Backspace"))); - table->setItem(4, 1, new QTableWidgetItem(QStringLiteral("Ctrl + Shift + Z"))); - table->setItem(5, 1, new QTableWidgetItem(QStringLiteral("F5"))); - table->setItem(6, 1, new QTableWidgetItem(QStringLiteral("Shift + F5"))); - table->setItem(7, 1, new QTableWidgetItem(QStringLiteral("Ctrl + F"))); - table->setItem(8, 1, new QTableWidgetItem(QStringLiteral("Ctrl + Shift + F"))); - table->setItem(9, 1, new QTableWidgetItem(QStringLiteral("Ctrl + D | /"))); - table->setItem(10, 1, new QTableWidgetItem(QStringLiteral("Ctrl + N"))); - table->setItem(11, 1, new QTableWidgetItem(QStringLiteral("Ctrl + O"))); -# if defined(Q_OS_WIN32) - table->setItem(12, 1, new QTableWidgetItem("Alt + F4")); -# else - table->setItem(12, 1, new QTableWidgetItem(QStringLiteral("Ctrl + W"))); -# endif - table->setItem(13, 1, new QTableWidgetItem(QLatin1String(""))); - table->setItem(14, 1, new QTableWidgetItem(QStringLiteral("Ctrl + Home"))); - table->setItem(15, 1, new QTableWidgetItem(QStringLiteral("Ctrl + ,"))); - table->setItem(16, 1, new QTableWidgetItem(QStringLiteral("Ctrl + S"))); - table->setItem(17, 1, new QTableWidgetItem(QStringLiteral("Ctrl + B"))); -#endif - - table->updateSize(); + addShortcuts( + table, + {Shortcut(QStringLiteral("Switch Focus between Graph and Code"), QStringLiteral("Tab")), + Shortcut::defaultOrMac( + QStringLiteral("Larger Font"), QStringLiteral("Ctrl + +"), QStringLiteral("Cmd + +")), + Shortcut::defaultOrMac( + QStringLiteral("Smaller Font"), QStringLiteral("Ctrl + -"), QStringLiteral("Cmd + -")), + Shortcut::defaultOrMac( + QStringLiteral("Reset Font Size"), QStringLiteral("Ctrl + 0"), QStringLiteral("Cmd + 0")), + Shortcut::defaultOrMac( + QStringLiteral("Back"), + QStringLiteral("Alt + Left | Z | Y | Backspace"), + QStringLiteral("Cmd + [ | Z | Y | Backspace")), + Shortcut::defaultOrMac( + QStringLiteral("Forward"), + QStringLiteral("Alt + Right | Shift + Z | Shift + Y"), + QStringLiteral("Cmd + ] | Shift + Z | Shift + Y")), + Shortcut::defaultOrMac( + QStringLiteral("Refresh"), QStringLiteral("F5"), QStringLiteral("Cmd + R")), + Shortcut::defaultOrMac( + QStringLiteral("Full Refresh"), + QStringLiteral("Shift + F5"), + QStringLiteral("Cmd + Shift + R")), + Shortcut::defaultOrMac( + QStringLiteral("Find Symbol"), QStringLiteral("Ctrl + F"), QStringLiteral("Cmd + F")), + Shortcut::defaultOrMac( + QStringLiteral("Find Text"), + QStringLiteral("Ctrl + Shift + F"), + QStringLiteral("Cmd + Shift + F")), + Shortcut::defaultOrMac( + QStringLiteral("Find On-Screen"), + QStringLiteral("Ctrl + D | /"), + QStringLiteral("Cmd + D | /")), + Shortcut::defaultOrMac( + QStringLiteral("New Project"), QStringLiteral("Ctrl + N"), QStringLiteral("Cmd + N")), + Shortcut::defaultOrMac( + QStringLiteral("Open Project"), QStringLiteral("Ctrl + O"), QStringLiteral("Cmd + O")), + Shortcut::winMacOrLinux( + QStringLiteral("Close Window"), + QStringLiteral("Alt + F4"), + QStringLiteral("Cmd + W"), + QStringLiteral("Ctrl + W")), + Shortcut::defaultOrMac( + QStringLiteral("Hide Window"), QStringLiteral(""), QStringLiteral("Cmd + H")), + Shortcut::defaultOrMac( + QStringLiteral("To Overview"), + QStringLiteral("Ctrl + Home"), + QStringLiteral("Cmd + Home | Cmd + Up")), + Shortcut::defaultOrMac( + QStringLiteral("Preferences"), QStringLiteral("Ctrl + ,"), QStringLiteral("Cmd + ,")), + Shortcut::defaultOrMac( + QStringLiteral("Bookmark Active Symbol"), + QStringLiteral("Ctrl + S"), + QStringLiteral("Cmd + S")), + Shortcut::defaultOrMac( + QStringLiteral("Bookmark Manager"), + QStringLiteral("Ctrl + B"), + QStringLiteral("Cmd + B"))}); return table; } @@ -182,25 +217,31 @@ QTableWidget* QtKeyboardShortcuts::createCodeViewShortcutsTable() { QtShortcutTable* table = createTableWidget("table_code"); - table->setRowCount(4); - table->setItem(0, 0, new QTableWidgetItem(QStringLiteral("Next Reference"))); - table->setItem(1, 0, new QTableWidgetItem(QStringLiteral("Previous Reference"))); - table->setItem(2, 0, new QTableWidgetItem(QStringLiteral("Next Local Reference"))); - table->setItem(3, 0, new QTableWidgetItem(QStringLiteral("Previous Local Reference"))); - -#if defined(Q_OS_MAC) - table->setItem(0, 1, new QTableWidgetItem("Cmd + G")); - table->setItem(1, 1, new QTableWidgetItem("Cmd + Shift + G")); - table->setItem(2, 1, new QTableWidgetItem("Cmd + T")); - table->setItem(3, 1, new QTableWidgetItem("Cmd + Shift + T")); -#else - table->setItem(0, 1, new QTableWidgetItem(QStringLiteral("Ctrl + G"))); - table->setItem(1, 1, new QTableWidgetItem(QStringLiteral("Ctrl + Shift + G"))); - table->setItem(2, 1, new QTableWidgetItem(QStringLiteral("Ctrl + T"))); - table->setItem(3, 1, new QTableWidgetItem(QStringLiteral("Ctrl + Shift + T"))); -#endif - - table->updateSize(); + addShortcuts( + table, + {Shortcut(QStringLiteral("Move Focus Within Code"), QStringLiteral("WASD | HJKL | Arrows")), + Shortcut( + QStringLiteral("Move Focus to Closest Reference"), + QStringLiteral("Shift + WS | Shift + JK | Shift + Up/Down")), + Shortcut(QStringLiteral("Activate Focused Location"), QStringLiteral("Enter | E")), + Shortcut::defaultOrMac( + QStringLiteral("Activate Location in New Tab"), + QStringLiteral("Ctrl + Shift + Enter | Ctrl + Shift + E"), + QStringLiteral("Cmd + Shift + Enter | Cmd + Shift + E")), + Shortcut::defaultOrMac( + QStringLiteral("Next Reference"), QStringLiteral("Ctrl + G"), QStringLiteral("Cmd + G")), + Shortcut::defaultOrMac( + QStringLiteral("Previous Reference"), + QStringLiteral("Ctrl + Shift + G"), + QStringLiteral("Cmd + Shift + G")), + Shortcut::defaultOrMac( + QStringLiteral("Next Local Reference"), + QStringLiteral("Ctrl + L"), + QStringLiteral("Cmd + L")), + Shortcut::defaultOrMac( + QStringLiteral("Previous Local Reference"), + QStringLiteral("Ctrl + Shift + L"), + QStringLiteral("Cmd + Shift + L"))}); return table; } @@ -209,29 +250,31 @@ QTableWidget* QtKeyboardShortcuts::createGraphViewShortcutsTable() { QtShortcutTable* table = createTableWidget("table_graph"); - table->setRowCount(7); - table->setItem(0, 0, new QTableWidgetItem(QStringLiteral("Pan left"))); - table->setItem(1, 0, new QTableWidgetItem(QStringLiteral("Pan right"))); - table->setItem(2, 0, new QTableWidgetItem(QStringLiteral("Pan up"))); - table->setItem(3, 0, new QTableWidgetItem(QStringLiteral("Pan down"))); - table->setItem(4, 0, new QTableWidgetItem(QStringLiteral("Zoom in"))); - table->setItem(5, 0, new QTableWidgetItem(QStringLiteral("Zoom out"))); - table->setItem(6, 0, new QTableWidgetItem(QStringLiteral("Reset Zoom"))); - - table->setItem(0, 1, new QTableWidgetItem(QStringLiteral("A"))); - table->setItem(1, 1, new QTableWidgetItem(QStringLiteral("D"))); - table->setItem(2, 1, new QTableWidgetItem(QStringLiteral("W"))); - table->setItem(3, 1, new QTableWidgetItem(QStringLiteral("S"))); -#if defined(Q_OS_MAC) - table->setItem(4, 1, new QTableWidgetItem("Shift + W | Cmd + Mousewheel up")); - table->setItem(5, 1, new QTableWidgetItem("Shift + S | Cmd + Mousewheel down")); -#else - table->setItem(4, 1, new QTableWidgetItem(QStringLiteral("Shift + W | Ctrl + Mousewheel up"))); - table->setItem(5, 1, new QTableWidgetItem(QStringLiteral("Shift + S | Ctrl + Mousewheel down"))); -#endif - table->setItem(6, 1, new QTableWidgetItem(QStringLiteral("0"))); - - table->updateSize(); + addShortcuts( + table, + {Shortcut(QStringLiteral("Move Focus Within Nodes"), QStringLiteral("WASD | HJKL | Arrows")), + Shortcut( + QStringLiteral("Move Focus Within Edges"), + QStringLiteral("Shift + WASD | Shift + HJKL | Shift + Arrows")), + Shortcut(QStringLiteral("Activate Node/Edge"), QStringLiteral("Enter | E")), + Shortcut(QStringLiteral("Expand/Collaps Node"), QStringLiteral("Shift + Enter | Shift + E")), + Shortcut::defaultOrMac( + QStringLiteral("Activate Node in New Tab"), + QStringLiteral("Ctrl + Shift + Enter | Ctrl + Shift + E"), + QStringLiteral("Cmd + Shift + Enter | Cmd + Shift + E")), + Shortcut::defaultOrMac( + QStringLiteral("Zoom in"), + QStringLiteral("Alt + W | Ctrl + Mousewheel up"), + QStringLiteral("Alt + W | Cmd + Mousewheel up")), + Shortcut::defaultOrMac( + QStringLiteral("Zoom out"), + QStringLiteral("Alt + S | Ctrl + Mousewheel down"), + QStringLiteral("Alt + S | Cmd + Mousewheel down")), + Shortcut(QStringLiteral("Reset Zoom"), QStringLiteral("0")), + Shortcut::defaultOrMac( + QStringLiteral("Open Custom Trail Dialog"), + QStringLiteral("Ctrl + U"), + QStringLiteral("Cmd + U"))}); return table; } diff --git a/src/lib_gui/qt/window/QtKeyboardShortcuts.h b/src/lib_gui/qt/window/QtKeyboardShortcuts.h index b2003ed1..dccb7332 100644 --- a/src/lib_gui/qt/window/QtKeyboardShortcuts.h +++ b/src/lib_gui/qt/window/QtKeyboardShortcuts.h @@ -30,7 +30,19 @@ public: virtual void windowReady() override; private: + struct Shortcut + { + const QString name; + const QString shortcut; + + Shortcut(const QString& name, const QString& shortcut); + static Shortcut defaultOrMac(const QString& name, const QString& defaultShortcut, const QString& macShortcut); + static Shortcut winMacOrLinux( + const QString& name, const QString& winShortcut, const QString& macShortcut, const QString& linuxShortcut); + }; + QtShortcutTable* createTableWidget(const std::string& objectName); + void addShortcuts(QtShortcutTable* table, const std::vector& shortcuts) const; QTableWidget* createGenerelShortcutsTable(); QTableWidget* createCodeViewShortcutsTable(); diff --git a/src/lib_gui/qt/window/QtMainWindow.cpp b/src/lib_gui/qt/window/QtMainWindow.cpp index 896a8d69..ec994b84 100644 --- a/src/lib_gui/qt/window/QtMainWindow.cpp +++ b/src/lib_gui/qt/window/QtMainWindow.cpp @@ -25,6 +25,7 @@ #include "MessageCustomTrailShow.h" #include "MessageErrorsHelpMessage.h" #include "MessageFind.h" +#include "MessageFocusView.h" #include "MessageHistoryRedo.h" #include "MessageHistoryUndo.h" #include "MessageIndexingShowDialog.h" @@ -469,6 +470,10 @@ void QtMainWindow::keyPressEvent(QKeyEvent* event) case Qt::Key_Space: PRINT_TRACES(); break; + + case Qt::Key_Tab: + MessageFocusView(MessageFocusView::ViewType::TOGGLE).dispatch(); + break; } } @@ -490,6 +495,12 @@ void QtMainWindow::resizeEvent(QResizeEvent* event) QMainWindow::resizeEvent(event); } +bool QtMainWindow::focusNextPrevChild(bool next) +{ + // makes tab key available in key press event + return false; +} + void QtMainWindow::about() { QtAbout* aboutWindow = createWindow(); @@ -905,17 +916,17 @@ void QtMainWindow::setupEditMenu() tr("Next Local Reference"), this, &QtMainWindow::codeLocalReferenceNext, - QKeySequence(Qt::CTRL + Qt::Key_E)); + QKeySequence(Qt::CTRL + Qt::Key_L)); menu->addAction( tr("Previous Local Reference"), this, &QtMainWindow::codeLocalReferencePrevious, - QKeySequence(Qt::SHIFT + Qt::CTRL + Qt::Key_E)); + QKeySequence(Qt::SHIFT + Qt::CTRL + Qt::Key_L)); menu->addSeparator(); menu->addAction( - tr("Custom Trail..."), this, &QtMainWindow::customTrail, QKeySequence(Qt::CTRL + Qt::Key_L)); + tr("Custom Trail..."), this, &QtMainWindow::customTrail, QKeySequence(Qt::CTRL + Qt::Key_U)); menu->addSeparator(); @@ -997,8 +1008,8 @@ void QtMainWindow::setupHistoryMenu() m_historyMenu->clear(); } - m_historyMenu->addAction(tr("Back"), this, &QtMainWindow::undo, QKeySequence::Undo); - m_historyMenu->addAction(tr("Forward"), this, &QtMainWindow::redo, QKeySequence::Redo); + m_historyMenu->addAction(tr("Back"), this, &QtMainWindow::undo, QKeySequence::Back); + m_historyMenu->addAction(tr("Forward"), this, &QtMainWindow::redo, QKeySequence::Forward); m_historyMenu->addSeparator(); diff --git a/src/lib_gui/qt/window/QtMainWindow.h b/src/lib_gui/qt/window/QtMainWindow.h index d9d45f3d..5c16369f 100644 --- a/src/lib_gui/qt/window/QtMainWindow.h +++ b/src/lib_gui/qt/window/QtMainWindow.h @@ -95,6 +95,8 @@ protected: virtual void closeEvent(QCloseEvent* event) override; virtual void resizeEvent(QResizeEvent* event) override; + virtual bool focusNextPrevChild(bool next) override; + public slots: void about(); void openSettings(); diff --git a/testing/keyboard_controls/data/code_view.cpp b/testing/keyboard_controls/data/code_view.cpp new file mode 100644 index 00000000..aa45bfc3 --- /dev/null +++ b/testing/keyboard_controls/data/code_view.cpp @@ -0,0 +1,192 @@ + +#define CODE_TESTS + +#include "header.h" + +// NOTE: complete in both snippet and single mode + +// TEST: Move Focus between locations +// START ---------------------------------------------------------------------- + +int sum(int a, int b) // <- ACTION: focus location via hover +{ + return a + b; +} + +int diff(int a, int b) +{ + return a - b; +} + +int mult(int a, int b) +{ + return a * b; +} + +int div(int a, int b) +{ + return a / b; +} + +// ACTION: Use WASD for navigation +// RESULTS: +// - Focus is moved between locations +// - lines without locations are skipped +// - focus tries to stay at same column if possible +// - view scrolls up and down if focus leaves visible viewport + +// ACTION: Use HJKL for navigation +// RESULTS: same as above + +// ACTION: Use arrow keys for navigation +// RESULTS: same as above + +// END ------------------------------------------------------------------------ + + + +// TEST: Activate location +// START ---------------------------------------------------------------------- + +int decimate(int a) // <- ACTION: move focus on 'decimate' +{ + return div(a, 10); +} + +// ACTION: Press Enter/E +// RESULT: 'decimate' is activated + +// END ------------------------------------------------------------------------ + + + +// TEST: Activate local symbol +// START ---------------------------------------------------------------------- + +int pow(int a, int b) +{ + int r = 1; // <- ACTION 1: move focus on 'r' + for (int i = 0; i < b; ++i) + { + r *= a; + } + return r; // <- ACTION 3: move focus on 'r' +} + +// ACTION 2: Press Enter/E +// RESULT 2: 'r' is activated + +// ACTION 4: Press Enter/E +// RESULT 3: 'r' is deactivated + +// END ------------------------------------------------------------------------ + + + +// TEST: Navigate between active locations +// START ---------------------------------------------------------------------- + +int square(int a) +{ + return mult(a, a); // <- ACTION: activate 'mult' +} + +// ACTION: Hold Shift and navigate with keyboard +// RESULT: focus is moved between the active locations of 'mult' + +// END ------------------------------------------------------------------------ + + +// TEST: Navigate between active local locations +// START ---------------------------------------------------------------------- + +int cube(int a) +{ + int r = square(a); // <- ACTION 1: activate 'square' + r = mult(r, a); + return r; // <- ACTION 2: activate 'r' +} + +// ACTION: Hold Shift and navigate with keyboard +// RESULTS: +// - focus is moved between the active locations of 'square' +// - focus is moved between the active local locations of 'r' inbetween + +// END ------------------------------------------------------------------------ + + + +// NOTE: Snippet mode only + + + +// TEST: Expanding scopes +// START ---------------------------------------------------------------------- + +// ACTION 5: Move focus to file expansion above and press Enter/E + +class Calculator +{ +public: + int getValue() const + { + return m_value; + } + + // ACTION 4: Move focus to scope expansion above and press Enter/E + void clearValue() + { + m_value = 0; + } + + void add(int a) // <- ACTION 1: activate 'add' + { + m_value += a; + } + // ACTION 2: Move focus to scope expansion below and press Enter/E + + void diff(int a) + { + m_value -= a; + } + // ACTION 3: Move focus to scope expansion above and press Enter/E + + void mult(int a) + { + m_value *= a; + } + + void div(int a) + { + m_value /= a; + } + +private: + int m_value = 0; +}; + +// RESULTS 1-4: +// - scope is expanded and focus is placed to location closest to expanded line + +// BROKEN: focus is placed but not visible +// RESULT 5: Whole file is expanded and focus placed at closest location + +// END ------------------------------------------------------------------------ + + + +// TEST: Expand/Minimize snippets +// START ---------------------------------------------------------------------- + +int half(int a) // <- ACTION 2: activate 'int' +{ + return div(a, 2); +} + +// ACTION 2: Move focus to snippet title bar and press Enter/E +// RESULT 2: Snippet is collapsed + +// ACTION 2: Press Enter/E again +// RESULT 2: Snippet is expanded again + +// END ------------------------------------------------------------------------ diff --git a/testing/keyboard_controls/data/graph_tests.cpp b/testing/keyboard_controls/data/graph_tests.cpp new file mode 100644 index 00000000..17ed5c20 --- /dev/null +++ b/testing/keyboard_controls/data/graph_tests.cpp @@ -0,0 +1,157 @@ + +#define GRAPH_TESTS + +// TEST: Move Focus between nodes +// START ---------------------------------------------------------------------- + +class X; +class Y; +class Z; + +class A +{ + int a; + int b; +}; + +class B : public A // <- ACTION: Activate B +{ + X* x; + Y* y; + Z* z; +}; + +class C : public B +{ + int e; +}; + +class D +{ + B b; +}; + +// ACTION: Press Tab +// RESULTS: +// - graph view is focused +// - Node 'B' is focused + +// ACTION: Use WASD for navigation +// RESULTS: +// - Focus is moved between nodes +// - Going down from 'B' focuses it's members +// - Going up from 'C' focuses 'B's members + +// ACTION: Use HJKL for navigation +// RESULTS: same as above + +// ACTION: Use arrow keys for navigation +// RESULTS: same as above + +// END ------------------------------------------------------------------------ + + + +// TEST: Move Focus between edges +// START ---------------------------------------------------------------------- + +// ACTION: Hold Shift and use WASD for navigation +// RESULTS: +// - Focus is moved between edges +// - Focus is moved to node if no more edges in that direction + +// ACTION: Hold Shift and use HJKL for navigation +// RESULTS: same as above + +// ACTION: Hold Shift and use arrow keys for navigation +// RESULTS: same as above + +// END ------------------------------------------------------------------------ + + + +// TEST: Moving focus beyond viewport centers focus +// START ---------------------------------------------------------------------- + +// ACTION: Drag classes far apart +// ACTION: Zoom in so not all nodes are visible +// ACTION: Navigate focus with keyboard +// RESULT: View is panned when focused node is outside of the viewport + +// END ------------------------------------------------------------------------ + + + +// TEST: Expand/collapse nodes +// START ---------------------------------------------------------------------- + +// ACTION: Move focus on class 'A' +// ACTION: Hold Shift and press Enter/E +// RESULT: 'A' is expanded + +// ACTION: Move focus to 'A::a' +// ACTION: Hold Shift and press Enter/E +// RESULTS: +// - 'A' is collapsed +// - 'A' is focused + +// END ------------------------------------------------------------------------ + + + +// TEST: Activate node +// START ---------------------------------------------------------------------- + +// ACTION: Move focus on class 'D' +// ACTION: Press Enter/E +// RESULT: 'D' is activated + +// END ------------------------------------------------------------------------ + + + +// TEST: Activate node in new Tab +// START ---------------------------------------------------------------------- + +// ACTION: Move focus on 'D::b' +// ACTION: Hold Ctrl/CMD + SHIFT and press Enter/E +// RESULT: 'D::b' is opened in new tab + +// END ------------------------------------------------------------------------ + + + +// TEST: Activate edge +// START ---------------------------------------------------------------------- + +B b; // <- ACTION: activate 'B' + +// ACTION: Move focus on type use 'b -> B' +// ACTION: Press Enter/E +// RESULT: edge is activated + +// END ------------------------------------------------------------------------ + + + +// TEST: Activate aggregation edge +// START ---------------------------------------------------------------------- + +// ACTION: Move focus on aggregation 'D -> B' +// ACTION: Press Enter/E +// RESULT: aggregation edge is activated + +// END ------------------------------------------------------------------------ + + + +// TEST: Expand bundle node +// START ---------------------------------------------------------------------- + +// ACTION: Move focus on 'Non-indexed Symbols' bundle +// ACTION: Press Enter/E +// RESULTS: +// - bundle is split +// - focus is moved to active symbol + +// END ------------------------------------------------------------------------ diff --git a/testing/keyboard_controls/data/header.h b/testing/keyboard_controls/data/header.h new file mode 100644 index 00000000..62e8a5b3 --- /dev/null +++ b/testing/keyboard_controls/data/header.h @@ -0,0 +1,10 @@ + +int sum(int a, int b); + +int diff(int a, int b); + +int mult(int a, int b); + +int div(int a, int b); + +int square(int a); diff --git a/testing/keyboard_controls/data/history_tests.cpp b/testing/keyboard_controls/data/history_tests.cpp new file mode 100644 index 00000000..53c40b35 --- /dev/null +++ b/testing/keyboard_controls/data/history_tests.cpp @@ -0,0 +1,59 @@ + +#define HISTORY_TESTS + +// TEST: Graph focus restored on back +// START ---------------------------------------------------------------------- + +class HistoryTest // <- ACTION: activate +{ +public: + int member; + int member2; +}; + +int func() +{ + HistoryTest h; + h.member = 0; +} + +// ACTION: Focus and activate 'func' in graph +// ACTION: Navigate 'back' in history +// RESULT: The graph for 'HistoryTest' is restored with focus at 'func' + +// END ------------------------------------------------------------------------ + + + +// TEST: Graph aggregation focus restored on back +// START ---------------------------------------------------------------------- + +class HistoryTestUser +{ +public: + int process() + { + h.member = 2; + h.member2 = 4; + } + + HistoryTest h; +}; + +// ACTION: Focus and activate aggregation 'HistoryTestUser -> HistoryTest' +// ACTION: Navigate 'back' in history +// RESULT: The graph for 'HistoryTest' is restored with focus at aggregation edge + +// END ------------------------------------------------------------------------ + + + +// TEST: Code focus restored on back +// START ---------------------------------------------------------------------- + +HistoryTest createTest(); // <- ACTION: focus and activate 'createTest' from code + +// ACTION: Navigate 'back' in history +// RESULT: The code view is restored with focus at 'createTest' + +// END ------------------------------------------------------------------------ diff --git a/testing/keyboard_controls/data/view_tests.cpp b/testing/keyboard_controls/data/view_tests.cpp new file mode 100644 index 00000000..d743f15b --- /dev/null +++ b/testing/keyboard_controls/data/view_tests.cpp @@ -0,0 +1,129 @@ + +#define VIEW_TESTS + +// TEST: Switch Focus with Tab +// START ---------------------------------------------------------------------- + +class ViewTestClass {}; // <- ACTION: Activate + +// ACTION: Press Tab continuously +// RESULT: Focus is moved between Graph and Code + +// END ------------------------------------------------------------------------ + + + +// TEST: Focus Search View +// START ---------------------------------------------------------------------- + +// ACTION: Use Find shortcut +// RESULT: Search view shows focus + +// ACTION: press Tab +// RESULT: Search View loses focus + +// ACTION: Click into search field +// RESULT: Search view shows focus + +// END ------------------------------------------------------------------------ + + + +// TEST: Focus Graph View +// START ---------------------------------------------------------------------- + +// ACTION: Hover nodes using the mouse +// RESULT: graph view receives focus + +// END ------------------------------------------------------------------------ + + + +// TEST: Focus Code View +// START ---------------------------------------------------------------------- + +ViewTestClass v; // <- ACTION: Hover location of 'v' + +// RESULT: code view receives focus + +// END ------------------------------------------------------------------------ + + + +// TEST: Graph remembers previously focused node +// START ---------------------------------------------------------------------- + +// ACTION: Hover a 'ViewTestFocus' in the graph + +// ACTION: Press Tab +// RESULT: code view receives focus + +// ACTION: Press Tab +// RESULTS: +// - graph receives focus +// - 'ViewTestFocus' is focused again + +// END ------------------------------------------------------------------------ + + + +// TEST: Code remembers previously focused location +// START ---------------------------------------------------------------------- + +ViewTestClass var; // <- ACTION: Hover location of 'var' + +// ACTION: Press Tab +// RESULT: graph view receives focus + +// ACTION: Press Tab +// RESULTS: +// - code receives focus +// - 'var' is focused again + +// END ------------------------------------------------------------------------ + + + +// TEST: Focus at active symbol when clicked in code +// START ---------------------------------------------------------------------- + +class ViewTestFocus // <- ACTION: Activate +{ + ViewTestClass member; +}; + +class ViewTestFocusDerived : public ViewTestFocus {}; + +// RESULTS: +// - Focus stays in code view +// - Clicked location has focus + +// ACTION: Press Tab +// RESULTS: +// - Focus is moved to graph +// - active node receives initial focus + +// END ------------------------------------------------------------------------ + + + +// TEST: Focus at active symbol when clicked in graph +// START ---------------------------------------------------------------------- + +class ViewTestFocusUser +{ + ViewTestFocus focus; +}; + +// ACTION: Activate 'ViewTestFocusUser' in graph + +// RESULTS: +// - Focus stays in graph view +// - Clicked node has focus + +// ACTION: Press Tab +// RESULTS: +// - Focus is moved to code +// - active location receives initial focus + +// END ------------------------------------------------------------------------ diff --git a/testing/keyboard_controls/keyboard_controls_tests.srctrlprj b/testing/keyboard_controls/keyboard_controls_tests.srctrlprj new file mode 100644 index 00000000..84bfc488 --- /dev/null +++ b/testing/keyboard_controls/keyboard_controls_tests.srctrlprj @@ -0,0 +1,33 @@ + + + Test Suites:\n\n [::\tmVIEW_TESTS\ts\tp]\n [::\tmGRAPH_TESTS\ts\tp]\n [::\tmCODE_TESTS\ts\tp]\n [::\tmHISTORY_TESTS\ts\tp]\n + + + c++17 + + + unknown + x86_64 + unknown + unknown + + 0 + + + data/files/template.h + + C++ Source Group + + .cpp + .cxx + .cc + + + data + + enabled + C++ Source Group + + + 8 +