From 5303187beaa59b1d56047130fbeb0c85b4544bea Mon Sep 17 00:00:00 2001 From: Eberhard Graether Date: Mon, 9 Apr 2018 00:09:22 +0200 Subject: [PATCH] ui: Group graph nodes by file or namespace (issues #171, #439, #522) * added buttons to enable grouping by file or namespace * last used group settings saved to ApplicationSettings * made groups hoverable/clickable on label only * improved node hatching and override edge colors * group overview lists fortune cookie message = Seen with the eyes of love, everything gets a new meaning. --- bin/app/data/color_schemes/bad_rainbow.xml | 42 ++- bin/app/data/color_schemes/bright.xml | 40 ++- bin/app/data/color_schemes/dark.xml | 42 ++- bin/app/data/gui/graph_view/graph_view.css | 21 +- .../gui/graph_view/images/group_namespace.png | Bin 0 -> 1436 bytes bin/app/user/ApplicationSettings_template.xml | 1 + src/lib/CMakeLists.txt | 2 + .../controller/ActivationController.cpp | 16 +- .../controller/BookmarkController.cpp | 6 +- .../component/controller/GraphController.cpp | 264 ++++++++++++++++-- .../component/controller/GraphController.h | 7 +- .../controller/helper/BucketLayouter.cpp | 7 +- .../controller/helper/BucketLayouter.h | 2 +- .../component/controller/helper/DummyNode.h | 54 +++- .../controller/helper/ListLayouter.cpp | 85 ++---- .../controller/helper/ListLayouter.h | 14 +- src/lib/component/view/GraphView.h | 2 + src/lib/component/view/GraphViewStyle.cpp | 55 +++- src/lib/component/view/GraphViewStyle.h | 5 +- src/lib/data/GroupType.cpp | 28 ++ src/lib/data/GroupType.h | 26 ++ src/lib/data/NodeType.h | 9 +- src/lib/data/access/StorageAccess.h | 1 + src/lib/data/access/StorageAccessProxy.cpp | 10 + src/lib/data/access/StorageAccessProxy.h | 1 + src/lib/data/storage/PersistentStorage.cpp | 22 ++ src/lib/data/storage/PersistentStorage.h | 1 + .../storage/sqlite/SqliteIndexStorage.cpp | 56 +++- .../data/storage/sqlite/SqliteIndexStorage.h | 6 +- src/lib/settings/ApplicationSettings.cpp | 10 + src/lib/settings/ApplicationSettings.h | 4 + .../messaging/type/MessageActivateNodes.h | 14 +- src/lib_gui/qt/view/QtGraphView.cpp | 95 ++++++- src/lib_gui/qt/view/QtGraphView.h | 7 + .../qt/view/graphElements/QtGraphEdge.cpp | 48 +++- .../qt/view/graphElements/QtGraphNode.cpp | 18 +- .../qt/view/graphElements/QtGraphNode.h | 6 +- .../qt/view/graphElements/QtGraphNodeData.cpp | 6 +- .../view/graphElements/QtGraphNodeGroup.cpp | 102 +++++-- .../qt/view/graphElements/QtGraphNodeGroup.h | 18 +- 40 files changed, 956 insertions(+), 197 deletions(-) create mode 100644 bin/app/data/gui/graph_view/images/group_namespace.png create mode 100644 src/lib/data/GroupType.cpp create mode 100644 src/lib/data/GroupType.h diff --git a/bin/app/data/color_schemes/bad_rainbow.xml b/bin/app/data/color_schemes/bad_rainbow.xml index 688a9d11..31bc0eae 100644 --- a/bin/app/data/color_schemes/bad_rainbow.xml +++ b/bin/app/data/color_schemes/bad_rainbow.xml @@ -344,6 +344,9 @@ #FFFFFF + + #B05151 + namespace @@ -352,7 +355,7 @@ #4E82F6 - #7FA9F4 + #6793F6 #20FFFFFF @@ -365,7 +368,7 @@ #FFFFFF - #999999 + #416CCC @@ -423,6 +426,41 @@ + + + transparent + + + #FFFFFF + + + #807F80 + + + + + transparent + + + #D4D4D4 + + + #4E82F6 + #6793F6 + + + + + transparent + + + #D4D4D4 + + + #873E3E + #944444 + + transparent diff --git a/bin/app/data/color_schemes/bright.xml b/bin/app/data/color_schemes/bright.xml index 1e218a82..65de7496 100644 --- a/bin/app/data/color_schemes/bright.xml +++ b/bin/app/data/color_schemes/bright.xml @@ -333,6 +333,9 @@ #3D3D3D + + #FFDEDE + namespace @@ -347,6 +350,9 @@ #203C3C3C #3C3C3C + + #D7F7C8 + file @@ -397,6 +403,38 @@ + + + transparent + + + #D0D0D0 + + + + + transparent + + + #5C5C5C + + + #BEE0AF + #A6D492 + + + + + transparent + + + #5C5C5C + + + #EFC5C5 + #EFB3B3 + + transparent @@ -413,7 +451,7 @@ - #955ECC + #A37ACC diff --git a/bin/app/data/color_schemes/dark.xml b/bin/app/data/color_schemes/dark.xml index c4ea6641..dbe2b91b 100644 --- a/bin/app/data/color_schemes/dark.xml +++ b/bin/app/data/color_schemes/dark.xml @@ -336,6 +336,9 @@ #C3C3C3 + + #521B1F + namespace @@ -344,12 +347,15 @@ #1B564E - #247368 + #21695F #20C3C3C3 #C3C3C3 + + #123B35 + file @@ -400,6 +406,38 @@ + + + transparent + + + #5A5A5A + + + + + transparent + + + #C3C3C3 + + + #1B564E + #1E6158 + + + + + transparent + + + #C3C3C3 + + + #78282D + #872D33 + + transparent @@ -416,7 +454,7 @@ - #B059C2 + #8A5A94 diff --git a/bin/app/data/gui/graph_view/graph_view.css b/bin/app/data/gui/graph_view/graph_view.css index ec848d86..7cb5bf64 100644 --- a/bin/app/data/gui/graph_view/graph_view.css +++ b/bin/app/data/gui/graph_view/graph_view.css @@ -14,7 +14,7 @@ QPushButton:disabled { background: ; } -QPushButton:hover { +QPushButton:hover, QPushButton:checked { background: ; } @@ -25,6 +25,7 @@ QPushButton:pressed { #expand_button { max-width: 26px; max-height: 26px; + border-radius: 9px; } #collapse_button { @@ -82,3 +83,21 @@ QPushButton:pressed { #depth_slider::groove:disabled, #depth_slider::handle:disabled { background: ; } + +#group_left_button { + border-radius: 0; + border-top-left-radius: 10px; + border-bottom-left-radius: 10px; + max-width: 26px; + max-height: 26px; + padding-left: 2px; +} + +#group_right_button { + border-radius: 0; + border-top-right-radius: 10px; + border-bottom-right-radius: 10px; + max-width: 26px; + max-height: 26px; + padding-right: 2px; +} diff --git a/bin/app/data/gui/graph_view/images/group_namespace.png b/bin/app/data/gui/graph_view/images/group_namespace.png new file mode 100644 index 0000000000000000000000000000000000000000..27e319920b7fa1d1e75664b1b9763452ba6eddc1 GIT binary patch literal 1436 zcmeAS@N?(olHy`uVBq!ia0vp^0U*r51|<6gKdl8)jKx9jP7LeL$-D%zPiKZiltlRY zSS9D@>LsS+C#C9D0BS3v*iS0onb8|oS8W7AZUnTBv4iY|~0L>t&w5X)?UL1X1yl$uzQ zUlfv`pJV3;3OfbQ+{EAFUm{>`W56OJ3||N3^AyB8-0*> zk-QHHTd*iFaP7E&Ho+pwj!RSTtStirbG@gFV@L(#+c~}+zJW3a+BM`gC-qJVO!K;- zaa1faT4U1&rO3qO)+0`K?!2F7wwws}v^(`ZW6hLS9UikF4Y7Sj6My_Id{y=R?4I)T zmGAHV-}(Lj`ulf`pU?YzZs&f>?|ZAy?ajY+OXp03qycl<0iFse2_uKSf=|kMHmqNB z^StGUe^vZ-YOM8=|1Aa7S4zw}wXw&C%_l)E_T?cpgSRTdeVk{OXvf}QSvEK2lwsq{ zlBL}I-sxATZk`cTTROF8d4PKC-U(ZG?_+YkH+fC3=*B&B4>i_@3TH4e^Rk3(7lhF> zCLic*d@HtW*L#jTEse$odkxfV_>Y7?(6|-N^?f0?nzQEN@C9qaynEWWeh@G_#G(=U zgKNzcmG_;|4le833>JopL_J)OC|x+2rM^+z_72TjQSl zD|vgFQuTCJo7iTpRkd2?x!{bM%bYXK^(wzsSgmw?5X|$IVJGKT->qx4=AI6I;Qi~d zU1Vs?q>zun#@hUi?_W+mqg2+s%6rnRZL35wmLBNeQqNcB7(aEN%gn1>jh`A7;^ik; z7xpjOV&buUwr;>YpBGKDo4+W&iajf&!7ur?Mf-zsmb0nV{fx%*8UA6Oeg~&Z<%%bj zFJ{?y?3kSempoVEvUANJL|^1tcCC4Qfp`6@oXr78Ygp@!Hmx~%@``tw&{E#`#@U8t ztG8SeVt-fd|0MR2sn^^uq7hk{C!5V1J*DL`g0y{vTmdKYv%m j)?3{!0_TUPrXQsLK3cjuPvP!tP&w-9>gTe~DWM4fUFt#z literal 0 HcmV?d00001 diff --git a/bin/app/user/ApplicationSettings_template.xml b/bin/app/user/ApplicationSettings_template.xml index d74522f3..cede33f6 100644 --- a/bin/app/user/ApplicationSettings_template.xml +++ b/bin/app/user/ApplicationSettings_template.xml @@ -32,6 +32,7 @@ + diff --git a/src/lib/CMakeLists.txt b/src/lib/CMakeLists.txt index a50a17fd..84e7e39e 100644 --- a/src/lib/CMakeLists.txt +++ b/src/lib/CMakeLists.txt @@ -265,6 +265,8 @@ add_files( data/ErrorCountInfo.h data/ErrorFilter.h data/ErrorInfo.h + data/GroupType.cpp + data/GroupType.h data/HierarchyCache.cpp data/HierarchyCache.h data/NodeType.cpp diff --git a/src/lib/component/controller/ActivationController.cpp b/src/lib/component/controller/ActivationController.cpp index 20a95af8..6dd4c31b 100644 --- a/src/lib/component/controller/ActivationController.cpp +++ b/src/lib/component/controller/ActivationController.cpp @@ -79,12 +79,22 @@ void ActivationController::handleMessage(MessageActivateNodes* message) MessageActivateTokens m(message); for (const MessageActivateNodes::ActiveNode& node : message->nodes) { - Id nodeId = node.nodeId ? node.nodeId : m_storageAccess->getNodeIdForNameHierarchy(node.nameHierarchy); + Id nodeId = node.nodeId; + NameHierarchy name = node.nameHierarchy; + if (!nodeId) + { + nodeId = m_storageAccess->getNodeIdForNameHierarchy(name); + } + else if (!name.size()) + { + name = m_storageAccess->getNameHierarchyForNodeId(nodeId); + } + if (nodeId > 0) { m.tokenIds.push_back(nodeId); } - m.tokenNames.push_back(node.nameHierarchy); + m.tokenNames.push_back(name); } m.searchMatches = m_storageAccess->getSearchMatchesForTokenIds(m.tokenIds); m.dispatchImmediately(); @@ -104,7 +114,7 @@ void ActivationController::handleMessage(MessageActivateSourceLocations* message MessageActivateNodes m; for (Id nodeId : m_storageAccess->getNodeIdsForLocationIds(message->locationIds)) { - m.addNode(nodeId, m_storageAccess->getNameHierarchyForNodeId(nodeId)); + m.addNode(nodeId); } m.dispatchImmediately(); } diff --git a/src/lib/component/controller/BookmarkController.cpp b/src/lib/component/controller/BookmarkController.cpp index d3be4d23..e5148728 100644 --- a/src/lib/component/controller/BookmarkController.cpp +++ b/src/lib/component/controller/BookmarkController.cpp @@ -188,9 +188,7 @@ void BookmarkController::activateBookmark(const std::shared_ptr bookma Id activeNodeId = edgeBookmark->getActiveNodeId(); if (activeNodeId) { - MessageActivateNodes activateNodes; - activateNodes.addNode(activeNodeId, m_storageAccess->getNameHierarchyForNodeId(activeNodeId)); - activateNodes.dispatch(); + MessageActivateNodes(activeNodeId).dispatch(); } MessageActivateEdge(firstEdgeId, Edge::intToType(storageEdge.type), sourceName, targetName).dispatch(); @@ -216,7 +214,7 @@ void BookmarkController::activateBookmark(const std::shared_ptr bookma for (Id nodeId: nodeBookmark->getNodeIds()) { - activateNodes.addNode(nodeId, m_storageAccess->getNameHierarchyForNodeId(nodeId)); + activateNodes.addNode(nodeId); } activateNodes.dispatch(); diff --git a/src/lib/component/controller/GraphController.cpp b/src/lib/component/controller/GraphController.cpp index 548bab85..d2135dc5 100644 --- a/src/lib/component/controller/GraphController.cpp +++ b/src/lib/component/controller/GraphController.cpp @@ -109,6 +109,16 @@ void GraphController::handleMessage(MessageActivateTokens* message) if (isNamespace) { addCharacterIndex(); + + DummyNode* group = groupAllNodes(GroupType::NAMESPACE, tokenIds[0]); + group->groupLayout = GroupLayout::LIST; + + if (!group->name.size()) + { + group->name = m_storageAccess->getNameHierarchyForNodeId(tokenIds[0]).getQualifiedName(); + group->tokenId = tokenIds[0]; + } + layoutNesting(); layoutList(); } @@ -141,6 +151,8 @@ void GraphController::handleMessage(MessageActivateTokens* message) m_useBezierEdges = !isInheritanceChain; } + groupNodesByParents(getView()->getGrouping()); + layoutNesting(); layoutGraph(true); assignBundleIds(); @@ -187,7 +199,7 @@ void GraphController::handleMessage(MessageActivateTrail* message) if (message->trailType & Edge::EDGE_INHERITANCE) { - groupTrailNodes(NodeType::GROUP_INHERITANCE); + groupTrailNodes(GroupType::INHERITANCE); } layoutNesting(); @@ -241,11 +253,14 @@ void GraphController::handleMessage(MessageFocusOut *message) void GraphController::handleMessage(MessageGraphNodeBundleSplit* message) { + std::wstring name; for (size_t i = 0; i < m_dummyNodes.size(); i++) { DummyNode* node = m_dummyNodes[i].get(); if ((node->isBundleNode() || node->isGroupNode()) && node->tokenId == message->bundleId) { + name = node->name; + std::vector> nodes; if (node->isBundleNode()) { @@ -296,7 +311,7 @@ void GraphController::handleMessage(MessageGraphNodeBundleSplit* message) } } - relayoutGraph(message, false, true, message->layoutToList, message->layoutToList); + relayoutGraph(message, false, true, message->layoutToList, message->layoutToList, name); } void GraphController::handleMessage(MessageGraphNodeExpand* message) @@ -310,9 +325,7 @@ void GraphController::handleMessage(MessageGraphNodeExpand* message) { if (!message->isReplayed()) { - MessageActivateNodes msg; - msg.addNode(message->tokenId, m_storageAccess->getNameHierarchyForNodeId(message->tokenId)); - msg.dispatch(); + MessageActivateNodes(message->tokenId).dispatch(); } return; } @@ -433,7 +446,7 @@ void GraphController::handleMessage(MessageGraphNodeHide* message) if (node || edge) { - relayoutGraph(message, false, true, false, false); + relayoutGraph(message, false, true, false, false, L""); } } @@ -542,8 +555,8 @@ void GraphController::createDummyGraph(const std::shared_ptr graph) updateDummyNodeNamesAndAddQualifiers(dummyNodes); m_dummyNodes = dummyNodes; - m_graph = graph; + m_useBezierEdges = false; } @@ -662,7 +675,7 @@ void GraphController::updateDummyNodeNamesAndAddQualifiers( qualifierNode->visible = true; node->subNodes.push_back(qualifierNode); - node->hasQualifier = true; + node->qualifierName = qualifier; } } } @@ -986,7 +999,7 @@ void GraphController::bundleNodes() // std::cout << node->bundleInfo.layoutVertical << " "; // std::cout << node->bundleInfo.isReferenced << " "; // std::cout << node->bundleInfo.isReferencing << " "; - // std::cout << node->name << std::endl; + // std::wcout << node->name << std::endl; // } // bundle @@ -1380,7 +1393,174 @@ bool GraphController::hasCharacterIndex() const return false; } -void GraphController::groupTrailNodes(NodeType::GroupType groupType) +void GraphController::groupNodesByParents(GroupType groupType) +{ + TRACE(); + + if (groupType != GroupType::FILE && groupType != GroupType::NAMESPACE) + { + return; + } + + std::map> groupNodes; + std::map>> nodesToGroup; + + std::map> nodeIdtoParentMap; + if (groupType == GroupType::FILE) + { + std::vector nodeIds; + for (const std::shared_ptr& dummyNode : m_dummyNodes) + { + if (dummyNode->isGraphNode()) + { + nodeIds.push_back(dummyNode->tokenId); + } + } + + nodeIdtoParentMap = m_storageAccess->getNodeIdToParentFileMap(nodeIds); + } + + std::map qualifierNameToIdMap; + for (const std::shared_ptr& dummyNode : m_dummyNodes) + { + if (dummyNode->isGroupNode()) + { + groupNodes.emplace(dummyNode->name, dummyNode); + } + else if (dummyNode->visible) + { + if (groupType == GroupType::FILE) + { + if (dummyNode->isGraphNode()) + { + auto it = nodeIdtoParentMap.find(dummyNode->tokenId); + if (it != nodeIdtoParentMap.end()) + { + nodesToGroup[it->second.second.getQualifiedName()].push_back(dummyNode); + } + } + } + else if (groupType == GroupType::NAMESPACE) + { + const DummyNode* qualifierNode = dummyNode->getQualifierNode(); + if (qualifierNode) + { + Id qualifierId = 0; + std::wstring qualifierName = qualifierNode->qualifierName.getQualifiedName(); + auto it = qualifierNameToIdMap.find(qualifierName); + if (it != qualifierNameToIdMap.end()) + { + qualifierId = it->second; + } + else + { + qualifierId = m_storageAccess->getNodeIdForNameHierarchy(qualifierNode->qualifierName); + qualifierNameToIdMap.emplace(qualifierName, qualifierId); + } + + nodesToGroup[qualifierName].push_back(dummyNode); + nodeIdtoParentMap.emplace( + dummyNode->tokenId, std::make_pair(qualifierId, qualifierNode->qualifierName)); + } + } + } + } + + std::set groupedNodeIds; + for (const std::pair>>& p : nodesToGroup) + { + std::shared_ptr groupNode; + + std::wstring name = p.first; + if (groupType == GroupType::FILE) + { + name = FilePath(p.first).fileName(); + } + + auto it = groupNodes.find(name); + if (it != groupNodes.end()) + { + groupNode = it->second; + } + else + { + groupNode = std::make_shared(DummyNode::DUMMY_GROUP); + groupNode->visible = true; + groupNode->groupType = groupType; + groupNode->groupLayout = GroupLayout::BUCKET; + groupNode->name = name; + + auto it = nodeIdtoParentMap.find(p.second[0]->tokenId); + if (it != nodeIdtoParentMap.end()) + { + groupNode->tokenId = it->second.first; + } + m_topLevelAncestorIds[groupNode->tokenId] = groupNode->tokenId; + m_dummyNodes.push_back(groupNode); + } + + std::vector bundleInfos; + for (std::shared_ptr dummyNode : p.second) + { + if (dummyNode->hasActiveSubNode()) + { + groupNode->bundleInfo = dummyNode->bundleInfo; + groupNode->bundleId = dummyNode->bundleId; + } + else + { + bundleInfos.push_back(dummyNode->bundleInfo); + } + + groupNode->subNodes.push_back(dummyNode); + m_topLevelAncestorIds[dummyNode->tokenId] = groupNode->tokenId; + groupedNodeIds.insert(dummyNode->tokenId); + } + + if (!groupNode->bundleId) + { + groupNode->bundleId = groupNode->subNodes[0]->bundleId; + groupNode->bundleInfo = DummyNode::BundleInfo::averageBundleInfo(bundleInfos); + } + + groupNode->sortSubNodesByName(); + } + + for (int i = 0; i < int(m_dummyNodes.size()); i++) + { + if (groupedNodeIds.find(m_dummyNodes[i]->tokenId) != groupedNodeIds.end()) + { + m_dummyNodes.erase(m_dummyNodes.begin() + i); + i--; + } + } +} + +DummyNode* GraphController::groupAllNodes(GroupType groupType, Id groupNodeId) +{ + TRACE(); + + std::shared_ptr groupNode = std::make_shared(DummyNode::DUMMY_GROUP); + groupNode->visible = true; + groupNode->groupType = groupType; + groupNode->tokenId = groupNodeId; + m_topLevelAncestorIds[groupNode->tokenId] = groupNode->tokenId; + + for (std::shared_ptr dummyNode : m_dummyNodes) + { + groupNode->subNodes.push_back(dummyNode); + m_topLevelAncestorIds[dummyNode->tokenId] = groupNode->tokenId; + } + + if (groupNode->subNodes.size()) + { + m_dummyNodes = { groupNode }; + } + + return groupNode.get(); +} + +void GraphController::groupTrailNodes(GroupType groupType) { TRACE(); @@ -1455,6 +1635,7 @@ void GraphController::groupTrailNodes(NodeType::GroupType groupType) std::shared_ptr groupNode = std::make_shared(DummyNode::DUMMY_GROUP); groupNode->visible = true; groupNode->groupType = groupType; + groupNode->groupLayout = GroupLayout::SKEWED; // Use token Id of first node and make first 2 bits 1 groupNode->tokenId = ~(~size_t(0) >> 2) + node.nodeId; @@ -1626,6 +1807,10 @@ void GraphController::layoutNestingRecursive(DummyNode* node) const { width = margins.charWidth * node->name.size(); } + else if (node->isGroupNode()) + { + width = margins.charWidth * node->name.size() + 5; + } width += margins.iconWidth; width = std::max(width, margins.minWidth); @@ -1651,19 +1836,44 @@ void GraphController::layoutNestingRecursive(DummyNode* node) const } } - int top = margins.top + margins.charHeight + margins.spacingA; - int left = margins.left; - Vec2i size; if (node->isGroupNode()) { - size = ListLayouter::layoutSkewed( - &node->subNodes, top, left, margins.spacingX, margins.spacingY, getView()->getViewSize().x() * 1.5); + Vec2i viewSize = getView()->getViewSize(); + + switch (node->groupLayout) + { + case GroupLayout::LIST: + viewSize.x = viewSize.x - 150; // prevent horizontal scroll + ListLayouter::layoutMultiColumn(viewSize, &node->subNodes); + break; + + case GroupLayout::SKEWED: + ListLayouter::layoutSkewed(&node->subNodes, margins.spacingX, margins.spacingY, viewSize.x() * 1.5); + break; + + case GroupLayout::BUCKET: + if (node->hasActiveSubNode()) + { + BucketLayouter grid(viewSize); + grid.createBuckets(node->subNodes, m_dummyEdges); + grid.layoutBuckets(true); + node->subNodes = grid.getSortedNodes(); + } + else + { + ListLayouter::layoutColumn(&node->subNodes, margins.spacingY); + } + break; + } } else { - size = ListLayouter::layoutColumn(&node->subNodes, top, left, margins.spacingY); + ListLayouter::layoutColumn(&node->subNodes, margins.spacingY); } + Vec2i size = ListLayouter::offsetNodes( + node->subNodes, margins.top + margins.charHeight + margins.spacingA, margins.left); + width = std::max(size.x(), width); height = size.y(); @@ -1795,7 +2005,7 @@ void GraphController::layoutGraph(bool getSortedNodes) BucketLayouter grid(getView()->getViewSize()); grid.createBuckets(visibleNodes, m_dummyEdges); - grid.layoutBuckets(); + grid.layoutBuckets(false); if (getSortedNodes) { @@ -1891,7 +2101,8 @@ DummyEdge* GraphController::getDummyGraphEdgeById(Id tokenId) const } void GraphController::relayoutGraph( - MessageBase* message, bool centerActiveNode, bool animatedTransition, bool scrollToTop, bool withCharacterIndex) + MessageBase* message, bool centerActiveNode, bool animatedTransition, bool scrollToTop, bool withCharacterIndex, + const std::wstring& groupName) { bool showsTrail = m_graph->getTrailMode() != Graph::TRAIL_NONE; @@ -1900,11 +2111,28 @@ void GraphController::relayoutGraph( if (hasCharacterIndex() || withCharacterIndex) { addCharacterIndex(); + + if (withCharacterIndex && m_dummyNodes.size()) + { + // Use token Id of first node and make first 2 bits 1 + Id groupId = ~(~size_t(0) >> 2) + m_dummyNodes[0]->tokenId; + + DummyNode* group = groupAllNodes(GroupType::DEFAULT, groupId); + group->groupLayout = GroupLayout::LIST; + group->interactive = false; + group->name = groupName; + } + layoutNesting(); layoutList(); } else { + if (!showsTrail) + { + groupNodesByParents(getView()->getGrouping()); + } + layoutNesting(); if (showsTrail) diff --git a/src/lib/component/controller/GraphController.h b/src/lib/component/controller/GraphController.h index 820b8a7c..d0c0f12a 100644 --- a/src/lib/component/controller/GraphController.h +++ b/src/lib/component/controller/GraphController.h @@ -109,7 +109,9 @@ private: void addCharacterIndex(); bool hasCharacterIndex() const; - void groupTrailNodes(NodeType::GroupType groupType); + void groupNodesByParents(GroupType groupType); + DummyNode* groupAllNodes(GroupType groupType, Id groupNodeId); + void groupTrailNodes(GroupType groupType); void layoutNesting(); void layoutNestingRecursive(DummyNode* node) const; @@ -126,7 +128,8 @@ private: DummyEdge* getDummyGraphEdgeById(Id tokenId) const; void relayoutGraph( - MessageBase* message, bool centerActiveNode, bool animatedTransition, bool scrollToTop, bool withCharacterIndex); + MessageBase* message, bool centerActiveNode, bool animatedTransition, bool scrollToTop, bool withCharacterIndex, + const std::wstring& groupName); void buildGraph(MessageBase* message, bool centerActiveNode, bool animatedTransition, bool scrollToTop); void forEachDummyNodeRecursive(std::function func); diff --git a/src/lib/component/controller/helper/BucketLayouter.cpp b/src/lib/component/controller/helper/BucketLayouter.cpp index c0056aae..093c8f5a 100644 --- a/src/lib/component/controller/helper/BucketLayouter.cpp +++ b/src/lib/component/controller/helper/BucketLayouter.cpp @@ -194,7 +194,7 @@ void BucketLayouter::createBuckets( } } -void BucketLayouter::layoutBuckets() +void BucketLayouter::layoutBuckets(bool addVerticalOffset) { std::map widths; std::map heights; @@ -236,6 +236,11 @@ void BucketLayouter::layoutBuckets() Bucket* midBucket = &m_buckets[0][0]; yOff = (heights[0] - midBucket->getHeight()) / 2 * -j; } + // move every second bucket in a row slighly lower to avoid edges over nodes + else if (addVerticalOffset && std::abs(i) % 2 == 1) + { + yOff += GraphViewStyle::toGridGap(10); + } bucket->layout(x, y + yOff, widths[i], heights[j]); x += widths[i] + GraphViewStyle::toGridGap(85); diff --git a/src/lib/component/controller/helper/BucketLayouter.h b/src/lib/component/controller/helper/BucketLayouter.h index e9d312a6..98cbfc0d 100644 --- a/src/lib/component/controller/helper/BucketLayouter.h +++ b/src/lib/component/controller/helper/BucketLayouter.h @@ -44,7 +44,7 @@ public: void createBuckets( std::vector>& nodes, const std::vector>& edges); - void layoutBuckets(); + void layoutBuckets(bool addVerticalOffset); std::vector> getSortedNodes(); diff --git a/src/lib/component/controller/helper/DummyNode.h b/src/lib/component/controller/helper/DummyNode.h index 8e5cc558..ab444cb7 100644 --- a/src/lib/component/controller/helper/DummyNode.h +++ b/src/lib/component/controller/helper/DummyNode.h @@ -7,6 +7,7 @@ #include "utility/utilityString.h" #include "data/graph/Node.h" +#include "data/GroupType.h" #include "data/name/NameHierarchy.h" #include "data/parser/AccessKind.h" @@ -54,6 +55,32 @@ public: , isReferencing(false) {} + static BundleInfo averageBundleInfo(const std::vector& bundleInfos) + { + size_t activeCount = 0; + size_t definedCount = 0; + size_t verticalLayoutCount = 0; + size_t referencedCount = 0; + size_t referencingCount = 0; + + for (const BundleInfo& info : bundleInfos) + { + if (info.isActive) activeCount++; + if (info.isDefined) definedCount++; + if (info.layoutVertical) verticalLayoutCount++; + if (info.isReferenced) referencedCount++; + if (info.isReferencing) referencingCount++; + } + + BundleInfo info; + if (activeCount > bundleInfos.size() / 2) info.isActive = true; + if (definedCount > bundleInfos.size() / 2) info.isDefined = true; + if (verticalLayoutCount > bundleInfos.size() / 2) info.layoutVertical = true; + if (referencedCount > bundleInfos.size() / 2) info.isReferenced = true; + if (referencingCount > bundleInfos.size() / 2) info.isReferencing = true; + return info; + } + bool isActive; bool isDefined; bool layoutVertical; @@ -73,7 +100,6 @@ public: , expanded(false) , autoExpanded(false) , hasParent(true) - , hasQualifier(false) , accessKind(ACCESS_NONE) , invisibleSubNodeCount(0) , bundleId(0) @@ -81,7 +107,9 @@ public: , bundledNodeCount(0) , bundledNodeType(NodeType::NODE_SYMBOL) , qualifierName(NAME_DELIMITER_UNKNOWN) - , groupType(NodeType::GROUP_FRAMELESS) + , groupType(GroupType::DEFAULT) + , groupLayout(GroupLayout::LIST) + , interactive(true) { } @@ -365,6 +393,23 @@ public: ); } + bool getsLayouted() const + { + return visible && !isExpandToggleNode() && !isQualifierNode(); + } + + const DummyNode* getQualifierNode() const + { + for (const std::shared_ptr& subNode : subNodes) + { + if (subNode->isQualifierNode()) + { + return subNode.get(); + } + } + return nullptr; + } + Type type; Vec2i position; @@ -387,7 +432,6 @@ public: bool expanded; bool autoExpanded; bool hasParent; - bool hasQualifier; // AccessNode AccessKind accessKind; @@ -411,8 +455,10 @@ public: NameHierarchy qualifierName; // GroupNode - NodeType::GroupType groupType; + GroupType groupType; + GroupLayout groupLayout; std::vector hiddenEdgeIds; + bool interactive; }; #endif // DUMMY_NODE_H diff --git a/src/lib/component/controller/helper/ListLayouter.cpp b/src/lib/component/controller/helper/ListLayouter.cpp index 8f1fdc40..bc310fbd 100644 --- a/src/lib/component/controller/helper/ListLayouter.cpp +++ b/src/lib/component/controller/helper/ListLayouter.cpp @@ -5,17 +5,17 @@ #include "component/controller/helper/DummyNode.h" #include "component/view/GraphViewStyle.h" -Vec2i ListLayouter::layoutRow(std::vector>* nodes, int top, int left, int gap) +void ListLayouter::layoutRow(std::vector>* nodes, int gap) { - return layoutSimple(nodes, top, left, gap, 0, true); + layoutSimple(nodes, gap, 0, true); } -Vec2i ListLayouter::layoutColumn(std::vector>* nodes, int top, int left, int gap) +void ListLayouter::layoutColumn(std::vector>* nodes, int gap) { - return layoutSimple(nodes, top, left, 0, gap, false); + layoutSimple(nodes, 0, gap, false); } -Vec2i ListLayouter::layoutMultiColumn(Vec2i viewSize, std::vector>* nodes) +void ListLayouter::layoutMultiColumn(Vec2i viewSize, std::vector>* nodes) { size_t colsFinal; std::vector maxWidthsFinal; @@ -26,7 +26,7 @@ Vec2i ListLayouter::layoutMultiColumn(Vec2i viewSize, std::vector> visibleNodes; for (auto node : *nodes) { - if (node->visible) + if (node->getsLayouted()) { visibleNodes.push_back(node); } @@ -82,8 +82,6 @@ Vec2i ListLayouter::layoutMultiColumn(Vec2i viewSize, std::vectorposition.y = y; y += visibleNodes[i]->size.y + gapY; - height = std::max(height, y); if (visibleNodes[i]->isTextNode()) { lastTextNode = visibleNodes[i]; } } - - for (int w : maxWidthsFinal) - { - width += w + gapX; - } - - return Vec2i(width - gapX, height - gapY); } -Vec2i ListLayouter::layoutSkewed( - std::vector>* nodes, int top, int left, int gapX, int gapY, int maxWidth) +void ListLayouter::layoutSkewed(std::vector>* nodes, int gapX, int gapY, int maxWidth) { std::vector> visibleNodes; std::multiset nodeWidths; for (auto node : *nodes) { - if (node->visible) + if (node->getsLayouted()) { visibleNodes.push_back(node); nodeWidths.insert(node->size.x()); @@ -185,8 +174,8 @@ Vec2i ListLayouter::layoutSkewed( } DummyNode* node = visibleNodes[i].get(); - node->position.x = left + x + (nodeWidth - node->size.x()) / 2; - node->position.y = top + height; + node->position.x = x + (nodeWidth - node->size.x()) / 2; + node->position.y = height; rowHeight = std::max(rowHeight, node->size.y()); x += nodeWidth + gapX; @@ -201,16 +190,6 @@ Vec2i ListLayouter::layoutSkewed( break; } } - - Vec4i rect = boundingRect(visibleNodes); - Vec2i offset(left - rect.x(), top - rect.y()); - - for (auto node : visibleNodes) - { - node->position += offset; - } - - return Vec2i(rect.z() - rect.x(), rect.w() - rect.y()); } Vec4i ListLayouter::boundingRect(const std::vector>& nodes) @@ -219,7 +198,7 @@ Vec4i ListLayouter::boundingRect(const std::vector>& for (auto node : nodes) { - if (!node->visible) + if (!node->getsLayouted()) { continue; } @@ -243,46 +222,44 @@ Vec4i ListLayouter::boundingRect(const std::vector>& return rect; } -Vec2i ListLayouter::layoutSimple( - std::vector>* nodes, int top, int left, int gapX, int gapY, bool horizontal) +Vec2i ListLayouter::offsetNodes(std::vector> nodes, int top, int left) +{ + Vec4i rect = boundingRect(nodes); + Vec2i offset(left - rect.x(), top - rect.y()); + + for (auto node : nodes) + { + if (node->getsLayouted()) + { + node->position += offset; + } + } + + return Vec2i(rect.z() - rect.x(), rect.w() - rect.y()); +} + +void ListLayouter::layoutSimple(std::vector>* nodes, int gapX, int gapY, bool horizontal) { int y = 0; int x = 0; - int width = 0; - int height = 0; - for (const std::shared_ptr& node : *nodes) { - if (!node->visible || node->isExpandToggleNode() || node->isQualifierNode()) + if (!node->getsLayouted()) { continue; } - node->position.x = left + x; - node->position.y = top + y; + node->position.x = x; + node->position.y = y; if (horizontal) { x += node->size.x + gapX; - height = std::max(height, node->size.y()); } else { y += node->size.y + gapY; - width = std::max(width, node->size.x()); } } - - if (x > 0) - { - width = x - gapX; - } - - if (y > 0) - { - height = y - gapY; - } - - return Vec2i(width, height); } diff --git a/src/lib/component/controller/helper/ListLayouter.h b/src/lib/component/controller/helper/ListLayouter.h index b72a563b..e91d0ac0 100644 --- a/src/lib/component/controller/helper/ListLayouter.h +++ b/src/lib/component/controller/helper/ListLayouter.h @@ -12,17 +12,17 @@ struct DummyNode; class ListLayouter { public: - static Vec2i layoutRow(std::vector>* nodes, int top, int left, int gap); - static Vec2i layoutColumn(std::vector>* nodes, int top, int left, int gap); + static void layoutRow(std::vector>* nodes, int gap); + static void layoutColumn(std::vector>* nodes, int gap); - static Vec2i layoutMultiColumn(Vec2i viewSize, std::vector>* nodes); - static Vec2i layoutSkewed( - std::vector>* nodes, int top, int left, int gapX, int gapY, int maxWidth); + static void layoutMultiColumn(Vec2i viewSize, std::vector>* nodes); + static void layoutSkewed(std::vector>* nodes, int gapX, int gapY, int maxWidth); static Vec4i boundingRect(const std::vector>& nodes); + static Vec2i offsetNodes(std::vector> nodes, int top, int left); + private: - static Vec2i layoutSimple( - std::vector>* nodes, int top, int left, int gapX, int gapY, bool horizontal); + static void layoutSimple(std::vector>* nodes, int gapX, int gapY, bool horizontal); }; #endif // LIST_LAYOUTER_H diff --git a/src/lib/component/view/GraphView.h b/src/lib/component/view/GraphView.h index 97f3fbef..dae5e791 100644 --- a/src/lib/component/view/GraphView.h +++ b/src/lib/component/view/GraphView.h @@ -8,6 +8,7 @@ #include "component/controller/helper/ScreenSearchInterfaces.h" #include "component/view/View.h" +#include "data/GroupType.h" struct DummyEdge; struct DummyNode; @@ -47,6 +48,7 @@ public: virtual void resizeView() = 0; virtual Vec2i getViewSize() const = 0; + virtual GroupType getGrouping() const = 0; virtual void scrollToValues(int xValue, int yValue) = 0; diff --git a/src/lib/component/view/GraphViewStyle.cpp b/src/lib/component/view/GraphViewStyle.cpp index fbb82523..1e94b36f 100644 --- a/src/lib/component/view/GraphViewStyle.cpp +++ b/src/lib/component/view/GraphViewStyle.cpp @@ -156,6 +156,9 @@ size_t GraphViewStyle::getFontSizeForStyleType(NodeType::StyleType type) case NodeType::STYLE_SMALL_NODE: return s_fontSize - 3; + case NodeType::STYLE_GROUP: + return s_fontSize - 2; + default: return s_fontSize; } @@ -188,7 +191,7 @@ size_t GraphViewStyle::getFontSizeOfTextNode() size_t GraphViewStyle::getFontSizeOfGroupNode() { - return s_fontSize; + return getFontSizeForStyleType(NodeType::STYLE_GROUP); } std::string GraphViewStyle::getFontNameForDataNode() @@ -225,6 +228,7 @@ GraphViewStyle::NodeMargins GraphViewStyle::getMarginsForDataNode(NodeType::Styl switch (type) { case NodeType::STYLE_PACKAGE: + case NodeType::STYLE_GROUP: margins.left = margins.right = 5; margins.top = margins.bottom = 3; margins.iconWidth = s_fontSize - 3; @@ -331,20 +335,20 @@ GraphViewStyle::NodeMargins GraphViewStyle::getMarginsOfTextNode() return margins; } -GraphViewStyle::NodeMargins GraphViewStyle::getMarginsOfGroupNode(NodeType::GroupType type, bool hasName) +GraphViewStyle::NodeMargins GraphViewStyle::getMarginsOfGroupNode(GroupType type, bool hasName) { NodeMargins margins; margins.spacingX = margins.spacingY = GraphViewStyle::s_gridCellPadding; - if (type == NodeType::GROUP_FRAMELESS) + if (type == GroupType::FRAMELESS) { return margins; } - margins.spacingA = (hasName ? 22 : 0); + margins.spacingA = (hasName ? 14 : 0); margins.left = margins.right = 20; - margins.top = (hasName ? 8 : 20); + margins.top = (hasName ? 12 : 20); margins.bottom = 20; if (hasName) @@ -352,7 +356,7 @@ GraphViewStyle::NodeMargins GraphViewStyle::getMarginsOfGroupNode(NodeType::Grou margins.minWidth = margins.charHeight = getFontSizeOfGroupNode(); } - margins.charWidth = getCharWidth(NodeType::STYLE_BIG_NODE); + margins.charWidth = getCharWidth(NodeType::STYLE_GROUP); return margins; } @@ -401,6 +405,7 @@ GraphViewStyle::NodeStyle GraphViewStyle::getStyleForNodeType( switch (type) { case NodeType::STYLE_PACKAGE: + case NodeType::STYLE_GROUP: style.cornerRadius = 0; style.textOffset.x = 5; style.textOffset.y = 3; @@ -555,27 +560,48 @@ GraphViewStyle::NodeStyle GraphViewStyle::getStyleOfTextNode() return style; } -GraphViewStyle::NodeStyle GraphViewStyle::getStyleOfGroupNode(NodeType::GroupType type, bool isFocused) +GraphViewStyle::NodeStyle GraphViewStyle::getStyleOfGroupNode(GroupType type, bool isFocused) { NodeStyle style; + style.cornerRadius = 15; + style.borderWidth = 2; + std::string colorType = "group/"; - if (type == NodeType::GROUP_INHERITANCE) + if (type == GroupType::DEFAULT) + { + colorType += "default"; + } + else if (type == GroupType::FILE) + { + colorType += "file"; + } + else if (type == GroupType::NAMESPACE) + { + colorType += "namespace"; + } + else if (type == GroupType::INHERITANCE) { colorType += "inheritance"; + + if (isFocused) + { + style.borderWidth = 3; + } + } + else + { + return style; } style.color = getNodeColor(colorType, isFocused); - style.cornerRadius = 15; - style.borderWidth = 2; - style.fontName = getFontNameOfGroupNode(); style.fontSize = getFontSizeOfGroupNode(); style.fontBold = true; - style.textOffset.x = 15; - style.textOffset.y = 6; + style.textOffset.x = 12; + style.textOffset.y = 5; return style; } @@ -638,7 +664,7 @@ GraphViewStyle::EdgeStyle GraphViewStyle::getStyleForEdgeType( style.verticalOffset = 6; break; case Edge::EDGE_INHERITANCE: - style.arrowLength = 20; + style.arrowLength = 17; style.arrowWidth = 14; style.arrowClosed = true; style.originOffset.x = 7; @@ -648,6 +674,7 @@ GraphViewStyle::EdgeStyle GraphViewStyle::getStyleForEdgeType( style.verticalOffset = 0; style.cornerRadius = 7; style.zValue = isActive ? 2 : -3; + style.width = isActive ? 3 : 2; if (isTrailEdge) { diff --git a/src/lib/component/view/GraphViewStyle.h b/src/lib/component/view/GraphViewStyle.h index 5c8bd7cf..c160fdcb 100644 --- a/src/lib/component/view/GraphViewStyle.h +++ b/src/lib/component/view/GraphViewStyle.h @@ -7,6 +7,7 @@ #include "utility/math/Vector2.h" #include "data/graph/Node.h" +#include "data/GroupType.h" #include "data/parser/AccessKind.h" class GraphViewStyleImpl; @@ -117,7 +118,7 @@ public: static NodeMargins getMarginsOfExpandToggleNode(); static NodeMargins getMarginsOfBundleNode(); static NodeMargins getMarginsOfTextNode(); - static NodeMargins getMarginsOfGroupNode(NodeType::GroupType type, bool hasName); + static NodeMargins getMarginsOfGroupNode(GroupType type, bool hasName); static NodeStyle getStyleForNodeType( NodeType type, bool defined, bool isActive, bool isFocused, bool hasChildren, bool hasQualifier); @@ -127,7 +128,7 @@ public: static NodeStyle getStyleOfBundleNode(bool isFocused); static NodeStyle getStyleOfQualifier(); static NodeStyle getStyleOfTextNode(); - static NodeStyle getStyleOfGroupNode(NodeType::GroupType type, bool isFocused); + static NodeStyle getStyleOfGroupNode(GroupType type, bool isFocused); static EdgeStyle getStyleForEdgeType(Edge::EdgeType type, bool isActive, bool isFocused, bool isTrailEdge); diff --git a/src/lib/data/GroupType.cpp b/src/lib/data/GroupType.cpp new file mode 100644 index 00000000..1efbe65b --- /dev/null +++ b/src/lib/data/GroupType.cpp @@ -0,0 +1,28 @@ +#include "data/GroupType.h" + +std::wstring groupTypeToString(GroupType type) +{ + switch (type) + { + case GroupType::NONE: return L"none"; + case GroupType::DEFAULT: return L"default"; + case GroupType::FRAMELESS: return L"frameless"; + case GroupType::FILE: return L"file"; + case GroupType::NAMESPACE: return L"namespace"; + case GroupType::INHERITANCE: return L"inheritance"; + } + + return L"none"; +} + +GroupType stringToGroupType(const std::wstring& value) +{ + if (value == groupTypeToString(GroupType::NONE)) return GroupType::NONE; + if (value == groupTypeToString(GroupType::DEFAULT)) return GroupType::DEFAULT; + if (value == groupTypeToString(GroupType::FRAMELESS)) return GroupType::FRAMELESS; + if (value == groupTypeToString(GroupType::FILE)) return GroupType::FILE; + if (value == groupTypeToString(GroupType::NAMESPACE)) return GroupType::NAMESPACE; + if (value == groupTypeToString(GroupType::INHERITANCE)) return GroupType::INHERITANCE; + + return GroupType::NONE; +} diff --git a/src/lib/data/GroupType.h b/src/lib/data/GroupType.h new file mode 100644 index 00000000..0ad2582c --- /dev/null +++ b/src/lib/data/GroupType.h @@ -0,0 +1,26 @@ +#ifndef GROUP_TYPE_H +#define GROUP_TYPE_H + +#include + +enum class GroupType +{ + NONE, + DEFAULT, + FRAMELESS, + FILE, + NAMESPACE, + INHERITANCE +}; + +std::wstring groupTypeToString(GroupType type); +GroupType stringToGroupType(const std::wstring& value); + +enum class GroupLayout +{ + LIST, + SKEWED, + BUCKET +}; + +#endif // GROUP_TYPE_H diff --git a/src/lib/data/NodeType.h b/src/lib/data/NodeType.h index 5e31aa0e..bb5bf1e2 100644 --- a/src/lib/data/NodeType.h +++ b/src/lib/data/NodeType.h @@ -48,13 +48,8 @@ public: { STYLE_PACKAGE = 0, STYLE_SMALL_NODE = 1, - STYLE_BIG_NODE = 2 - }; - - enum GroupType - { - GROUP_FRAMELESS, - GROUP_INHERITANCE + STYLE_BIG_NODE = 2, + STYLE_GROUP = 3 }; struct BundleInfo diff --git a/src/lib/data/access/StorageAccess.h b/src/lib/data/access/StorageAccess.h index 37e8bbd4..99079280 100644 --- a/src/lib/data/access/StorageAccess.h +++ b/src/lib/data/access/StorageAccess.h @@ -41,6 +41,7 @@ public: virtual NameHierarchy getNameHierarchyForNodeId(Id id) const = 0; virtual std::vector getNameHierarchiesForNodeIds(const std::vector& nodeIds) const = 0; + virtual std::map> getNodeIdToParentFileMap(const std::vector& nodeIds) const = 0; virtual NodeType getNodeTypeForNodeWithId(Id id) const = 0; diff --git a/src/lib/data/access/StorageAccessProxy.cpp b/src/lib/data/access/StorageAccessProxy.cpp index 46763a57..218ec5bd 100644 --- a/src/lib/data/access/StorageAccessProxy.cpp +++ b/src/lib/data/access/StorageAccessProxy.cpp @@ -85,6 +85,16 @@ std::vector StorageAccessProxy::getNameHierarchiesForNodeIds(cons return std::vector(); } +std::map> StorageAccessProxy::getNodeIdToParentFileMap(const std::vector& nodeIds) const +{ + if (hasSubject()) + { + return m_subject->getNodeIdToParentFileMap(nodeIds); + } + + return { }; +} + NodeType StorageAccessProxy::getNodeTypeForNodeWithId(Id id) const { if (hasSubject()) diff --git a/src/lib/data/access/StorageAccessProxy.h b/src/lib/data/access/StorageAccessProxy.h index c6a1d06d..bcb1936e 100644 --- a/src/lib/data/access/StorageAccessProxy.h +++ b/src/lib/data/access/StorageAccessProxy.h @@ -24,6 +24,7 @@ public: virtual NameHierarchy getNameHierarchyForNodeId(Id id) const override; virtual std::vector getNameHierarchiesForNodeIds(const std::vector& nodeIds) const override; + virtual std::map> getNodeIdToParentFileMap(const std::vector& nodeIds) const override; virtual NodeType getNodeTypeForNodeWithId(Id id) const override; diff --git a/src/lib/data/storage/PersistentStorage.cpp b/src/lib/data/storage/PersistentStorage.cpp index 7eb517dc..78cd6085 100644 --- a/src/lib/data/storage/PersistentStorage.cpp +++ b/src/lib/data/storage/PersistentStorage.cpp @@ -445,6 +445,28 @@ std::vector PersistentStorage::getNameHierarchiesForNodeIds(const return nameHierarchies; } +std::map> PersistentStorage::getNodeIdToParentFileMap(const std::vector& nodeIds) const +{ + std::map> nodeIdToParentFileMap; + + std::shared_ptr locations = m_sqliteIndexStorage.getSourceLocationsForElementIds(nodeIds); + locations->forEachSourceLocation( + [this, &nodeIdToParentFileMap](SourceLocation* location) + { + if (location->isStartLocation() && location->isScopeLocation()) + { + for (Id tokenId : location->getTokenIds()) + { + nodeIdToParentFileMap.emplace(tokenId, std::make_pair(getFileNodeId(location->getFilePath()), + NameHierarchy(location->getFilePath().wstr(), NAME_DELIMITER_FILE))); + } + } + } + ); + + return nodeIdToParentFileMap; +} + NodeType PersistentStorage::getNodeTypeForNodeWithId(Id nodeId) const { return utility::intToType(m_sqliteIndexStorage.getFirstById(nodeId).type); diff --git a/src/lib/data/storage/PersistentStorage.h b/src/lib/data/storage/PersistentStorage.h index 26154204..29008215 100644 --- a/src/lib/data/storage/PersistentStorage.h +++ b/src/lib/data/storage/PersistentStorage.h @@ -77,6 +77,7 @@ public: virtual NameHierarchy getNameHierarchyForNodeId(Id nodeId) const override; virtual std::vector getNameHierarchiesForNodeIds(const std::vector& nodeIds) const override; + virtual std::map> getNodeIdToParentFileMap(const std::vector& nodeIds) const override; virtual NodeType getNodeTypeForNodeWithId(Id nodeId) const override; diff --git a/src/lib/data/storage/sqlite/SqliteIndexStorage.cpp b/src/lib/data/storage/sqlite/SqliteIndexStorage.cpp index 23f15ebe..e0b87a9d 100644 --- a/src/lib/data/storage/sqlite/SqliteIndexStorage.cpp +++ b/src/lib/data/storage/sqlite/SqliteIndexStorage.cpp @@ -6,6 +6,9 @@ #include "utility/text/TextAccess.h" #include "utility/utilityString.h" +#include "data/location/SourceLocationCollection.h" +#include "data/location/SourceLocationFile.h" + const size_t SqliteIndexStorage::s_storageVersion = 15; SqliteIndexStorage::SqliteIndexStorage(const FilePath& dbFilePath) @@ -704,9 +707,60 @@ std::shared_ptr SqliteIndexStorage::getSourceLocationsOfType return getSourceLocationsForFile(filePath, "AND type == " + std::to_string(locationTypeToInt(type))); } +std::shared_ptr SqliteIndexStorage::getSourceLocationsForElementIds( + const std::vector& elementIds) const +{ + std::vector sourceLocationIds; + std::map> sourceLocationIdToElementIds; + for (const StorageOccurrence& occurrence : getOccurrencesForElementIds(elementIds)) + { + sourceLocationIds.push_back(occurrence.sourceLocationId); + sourceLocationIdToElementIds[occurrence.sourceLocationId].push_back(occurrence.elementId); + } + + CppSQLite3Query q = executeQuery( + "SELECT source_location.id, file.path, source_location.start_line, source_location.start_column, " + "source_location.end_line, source_location.end_column, source_location.type " + "FROM source_location INNER JOIN file ON (file.id = source_location.file_node_id) " + "WHERE source_location.id IN (" + utility::join(utility::toStrings(sourceLocationIds), ',') + ");" + ); + + std::shared_ptr ret = std::make_shared(); + + while (!q.eof()) + { + const Id id = q.getIntField(0, 0); + const std::string filePath = q.getStringField(1, ""); + const int startLineNumber = q.getIntField(2, -1); + const int startColNumber = q.getIntField(3, -1); + const int endLineNumber = q.getIntField(4, -1); + const int endColNumber = q.getIntField(5, -1); + const int type = q.getIntField(6, -1); + + if (id != 0 && filePath.size() && startLineNumber != -1 && startColNumber != -1 && endLineNumber != -1 && + endColNumber != -1 && type != -1) + { + ret->addSourceLocation( + intToLocationType(type), + id, + sourceLocationIdToElementIds[id], + FilePath(utility::decodeFromUtf8(filePath)), + startLineNumber, + startColNumber, + endLineNumber, + endColNumber + ); + } + + q.nextRow(); + } + + return ret; +} + std::vector SqliteIndexStorage::getOccurrencesForLocationId(Id locationId) const { - std::vector locationIds {locationId}; + std::vector locationIds { locationId }; return getOccurrencesForLocationIds(locationIds); } diff --git a/src/lib/data/storage/sqlite/SqliteIndexStorage.h b/src/lib/data/storage/sqlite/SqliteIndexStorage.h index 7514d7fd..7ec586b9 100644 --- a/src/lib/data/storage/sqlite/SqliteIndexStorage.h +++ b/src/lib/data/storage/sqlite/SqliteIndexStorage.h @@ -5,7 +5,7 @@ #include #include -#include "data/location/SourceLocationFile.h" +#include "data/location/LocationType.h" #include "data/storage/sqlite/SqliteDatabaseIndex.h" #include "data/storage/sqlite/SqliteStorage.h" #include "data/storage/type/StorageCommentLocation.h" @@ -24,6 +24,8 @@ class TextAccess; class Version; +class SourceLocationCollection; +class SourceLocationFile; struct ParseLocation; class SqliteIndexStorage @@ -95,6 +97,8 @@ public: std::shared_ptr getSourceLocationsOfTypeInFile( const FilePath& filePath, LocationType type) const; + std::shared_ptr getSourceLocationsForElementIds(const std::vector& elementIds) const; + std::vector getOccurrencesForLocationId(Id locationId) const; std::vector getOccurrencesForLocationIds(const std::vector& locationIds) const; std::vector getOccurrencesForElementIds(const std::vector& elementIds) const; diff --git a/src/lib/settings/ApplicationSettings.cpp b/src/lib/settings/ApplicationSettings.cpp index eb253322..267bc48e 100644 --- a/src/lib/settings/ApplicationSettings.cpp +++ b/src/lib/settings/ApplicationSettings.cpp @@ -241,6 +241,16 @@ void ApplicationSettings::setGraphControlsVisible(bool visible) setValue("application/graph_controls_visible", visible); } +GroupType ApplicationSettings::getGraphGrouping() const +{ + return stringToGroupType(getValue("application/graph_grouping", groupTypeToString(GroupType::NONE))); +} + +void ApplicationSettings::setGraphGrouping(GroupType type) +{ + setValue("application/graph_grouping", groupTypeToString(type)); +} + int ApplicationSettings::getScreenAutoScaling() const { return getValue("screen/auto_scaling", 1); diff --git a/src/lib/settings/ApplicationSettings.h b/src/lib/settings/ApplicationSettings.h index 78fa7a0a..4497496c 100644 --- a/src/lib/settings/ApplicationSettings.h +++ b/src/lib/settings/ApplicationSettings.h @@ -3,6 +3,7 @@ #include +#include "data/GroupType.h" #include "settings/Settings.h" class TimeStamp; @@ -63,6 +64,9 @@ public: bool getGraphControlsVisible() const; void setGraphControlsVisible(bool visible); + GroupType getGraphGrouping() const; + void setGraphGrouping(GroupType type); + // screen int getScreenAutoScaling() const; void setScreenAutoScaling(int autoScaling); diff --git a/src/lib/utility/messaging/type/MessageActivateNodes.h b/src/lib/utility/messaging/type/MessageActivateNodes.h index 828613d1..3db732e3 100644 --- a/src/lib/utility/messaging/type/MessageActivateNodes.h +++ b/src/lib/utility/messaging/type/MessageActivateNodes.h @@ -18,8 +18,19 @@ public: NameHierarchy nameHierarchy; }; - MessageActivateNodes() + MessageActivateNodes(Id tokenId = 0) { + if (tokenId > 0) + { + addNode(tokenId); + } + } + + void addNode(Id tokenId) + { + ActiveNode node; + node.nodeId = tokenId; + nodes.push_back(node); } void addNode(Id tokenId, const NameHierarchy& nameHierarchy) @@ -27,7 +38,6 @@ public: ActiveNode node; node.nodeId = tokenId; node.nameHierarchy = nameHierarchy; - nodes.push_back(node); } diff --git a/src/lib_gui/qt/view/QtGraphView.cpp b/src/lib_gui/qt/view/QtGraphView.cpp index def2027a..74a2d1a0 100644 --- a/src/lib_gui/qt/view/QtGraphView.cpp +++ b/src/lib_gui/qt/view/QtGraphView.cpp @@ -15,6 +15,7 @@ #include "utility/messaging/type/MessageActivateTrail.h" #include "utility/messaging/type/MessageDeactivateEdge.h" +#include "utility/messaging/type/MessageRefresh.h" #include "utility/messaging/type/MessageScrollGraph.h" #include "utility/messaging/type/MessageStatus.h" #include "utility/ResourcePaths.h" @@ -158,6 +159,52 @@ void QtGraphView::initView() updateTrailButtons(); trailDepthChanged(0); } + + // group controls + { + m_groupFileButton = new QtSelfRefreshIconButton( + "", ResourcePaths::getGuiPath().concatenate(L"graph_view/images/file.png"), "search/button"); + m_groupNamespaceButton = new QtSelfRefreshIconButton( + "", ResourcePaths::getGuiPath().concatenate(L"graph_view/images/group_namespace.png"), "search/button"); + + m_groupFileButton->setObjectName("group_right_button"); + m_groupNamespaceButton->setObjectName("group_left_button"); + + m_groupFileButton->setToolTip("group by file"); + m_groupNamespaceButton->setToolTip("group by package/namespace"); + + m_groupFileButton->setCheckable(true); + m_groupNamespaceButton->setCheckable(true); + + m_groupFileButton->setIconSize(QSize(14, 14)); + m_groupNamespaceButton->setIconSize(QSize(14, 14)); + + connect(m_groupFileButton, &QPushButton::clicked, [this](){ groupingUpdated(m_groupFileButton); }); + connect(m_groupNamespaceButton, &QPushButton::clicked, [this]() { groupingUpdated(m_groupNamespaceButton); }); + + GroupType type = ApplicationSettings::getInstance()->getGraphGrouping(); + if (type == GroupType::FILE) + { + m_groupFileButton->setChecked(true); + } + else if (type == GroupType::NAMESPACE) + { + m_groupNamespaceButton->setChecked(true); + } + + + m_groupWidget = new QWidget(widget); + m_groupWidget->setGeometry(38, 8, 54, 26); + + QHBoxLayout* layout = new QHBoxLayout(); + layout->setContentsMargins(0, 0, 0, 0); + layout->setSpacing(2); + + layout->addWidget(m_groupNamespaceButton); + layout->addWidget(m_groupFileButton); + + m_groupWidget->setLayout(layout); + } } void QtGraphView::refreshView() @@ -173,6 +220,8 @@ void QtGraphView::refreshView() view->setAppZoomFactor(GraphViewStyle::getZoomFactor()); m_trailWidget->setStyleSheet(css.c_str()); + m_groupWidget->setStyleSheet(css.c_str()); + updateTrailButtons(); }); } @@ -407,7 +456,7 @@ void QtGraphView::defocusTokenIds(const std::vector& defocusedTokenIds) for (const Id& tokenId : defocusedTokenIds) { QtGraphNode* node = findNodeRecursive(m_oldNodes, tokenId); - if (node && node->isDataNode()) + if (node && (node->isDataNode() || node->isGroupNode())) { node->focusOut(); continue; @@ -441,6 +490,20 @@ Vec2i QtGraphView::getViewSize() const return Vec2i((view->width() - 50) / zoomFactor, (view->height() - 100) / zoomFactor); } +GroupType QtGraphView::getGrouping() const +{ + if (m_groupFileButton->isChecked()) + { + return GroupType::FILE; + } + else if (m_groupNamespaceButton->isChecked()) + { + return GroupType::NAMESPACE; + } + + return GroupType::NONE; +} + void QtGraphView::scrollToValues(int xValue, int yValue) { m_restoreScroll = true; @@ -560,9 +623,17 @@ void QtGraphView::pressedCharacterKey(QChar c) const QtGraphNode* node = nullptr; bool hasTextNodes = false; - for (const QtGraphNode* n : m_oldNodes) + std::vector nodes(m_oldNodes.begin(), m_oldNodes.end()); + + size_t i = 0; + while (i < nodes.size()) { - if (n->isTextNode() && n->getName().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]); @@ -657,6 +728,20 @@ void QtGraphView::clickedForwardTrail() activateTrail(true); } +void QtGraphView::groupingUpdated(QPushButton* button) +{ + (button == m_groupFileButton ? m_groupNamespaceButton : m_groupFileButton)->setChecked(false); + + ApplicationSettings* appSettings = ApplicationSettings::getInstance().get(); + if (appSettings->getGraphGrouping() != getGrouping()) + { + appSettings->setGraphGrouping(getGrouping()); + appSettings->save(); + } + + MessageRefresh().refreshUiOnly().noReloadStyle().dispatch(); +} + MessageActivateTrail QtGraphView::getMessageActivateTrail(bool forward) { MessageActivateTrail message(0, 0, 0, 0, false); @@ -868,7 +953,7 @@ QtGraphNode* QtGraphView::createNodeRecursive( QtGraphNode* newNode = nullptr; if (node->isGraphNode()) { - newNode = new QtGraphNodeData(node->data, node->name, node->childVisible, node->hasQualifier); + newNode = new QtGraphNodeData(node->data, node->name, node->childVisible, node->getQualifierNode() != nullptr); } else if (node->isAccessNode()) { @@ -892,7 +977,7 @@ QtGraphNode* QtGraphView::createNodeRecursive( } else if (node->isGroupNode()) { - newNode = new QtGraphNodeGroup(node->tokenId, node->name, node->groupType); + newNode = new QtGraphNodeGroup(node->tokenId, node->name, node->groupType, node->interactive); } else { diff --git a/src/lib_gui/qt/view/QtGraphView.h b/src/lib_gui/qt/view/QtGraphView.h index c77377b8..6cfed72a 100644 --- a/src/lib_gui/qt/view/QtGraphView.h +++ b/src/lib_gui/qt/view/QtGraphView.h @@ -61,6 +61,7 @@ public: virtual void resizeView(); virtual Vec2i getViewSize() const; + virtual GroupType getGrouping() const; virtual void scrollToValues(int xValue, int yValue); @@ -84,6 +85,8 @@ private slots: void clickedBackwardTrail(); void clickedForwardTrail(); + void groupingUpdated(QPushButton* button); + private: MessageActivateTrail getMessageActivateTrail(bool forward); void activateTrail(bool forward); @@ -153,6 +156,10 @@ private: QSlider* m_trailDepthSlider; QLabel* m_trailDepthLabel; + QWidget* m_groupWidget; + QtSelfRefreshIconButton* m_groupFileButton; + QtSelfRefreshIconButton* m_groupNamespaceButton; + std::vector m_virtualNodeRects; // Name matches diff --git a/src/lib_gui/qt/view/graphElements/QtGraphEdge.cpp b/src/lib_gui/qt/view/graphElements/QtGraphEdge.cpp index 8985b501..1821d7b4 100644 --- a/src/lib_gui/qt/view/graphElements/QtGraphEdge.cpp +++ b/src/lib_gui/qt/view/graphElements/QtGraphEdge.cpp @@ -88,8 +88,8 @@ Id QtGraphEdge::getTokenId() const void QtGraphEdge::updateLine() { - QtGraphNode* owner = m_owner; - QtGraphNode* target = m_target; + const QtGraphNode* owner = m_owner; + const QtGraphNode* target = m_target; Edge::EdgeType type = (getData() ? getData()->getType() : Edge::EDGE_AGGREGATION); GraphViewStyle::EdgeStyle style = GraphViewStyle::getStyleForEdgeType(type, m_isActive | m_isFocused, false, m_isTrailEdge); @@ -97,8 +97,22 @@ void QtGraphEdge::updateLine() Vec4i ownerRect = owner->getBoundingRect(); Vec4i targetRect = target->getBoundingRect(); - Vec4i ownerParentRect = owner->getParentBoundingRect(); - Vec4i targetParentRect = target->getParentBoundingRect(); + Vec4i ownerParentRect; + Vec4i targetParentRect; + + const QtGraphNode* ownerNonGroupParent = owner->getLastNonGroupParent(); + const QtGraphNode* targetNonGroupParent = target->getLastNonGroupParent(); + + if (owner->getLastParent() == target->getLastParent() && owner->getLastParent()->isGroupNode()) + { + ownerParentRect = ownerNonGroupParent->getBoundingRect(); + targetParentRect = targetNonGroupParent->getBoundingRect(); + } + else + { + ownerParentRect = owner->getLastParent()->getBoundingRect(); + targetParentRect = target->getLastParent()->getBoundingRect(); + } if (m_useBezier) { @@ -142,7 +156,7 @@ void QtGraphEdge::updateLine() bezier->setRoute(route); bezier->setPivot(QtLineItemBase::PIVOT_MIDDLE); - if (owner->getLastParent() == target->getLastParent()) + if (ownerNonGroupParent == targetNonGroupParent) { if (ownerRect.y() < target->getBoundingRect().y()) { @@ -163,7 +177,7 @@ void QtGraphEdge::updateLine() QtLineItemAngled* child = dynamic_cast(m_child); - if (owner->getIsActive() && owner->getLastParent() == target->getLastParent()) + if (owner->getIsActive() && ownerNonGroupParent == targetNonGroupParent) { child->setOnBack(true); } @@ -172,7 +186,7 @@ void QtGraphEdge::updateLine() { child->setEarlyBend(true); - if (owner->getLastParent() == target->getLastParent() || + if (ownerNonGroupParent == targetNonGroupParent || (type == Edge::EDGE_OVERRIDE && targetParentRect.z() + style.targetOffset.x + style.originOffset.x > ownerParentRect.x())) { @@ -185,7 +199,7 @@ void QtGraphEdge::updateLine() } if (type == Edge::EDGE_INHERITANCE || (type == Edge::EDGE_TEMPLATE_SPECIALIZATION && - owner == owner->getLastParent() && target == target->getLastParent())) + owner == owner->getLastNonGroupParent() && target == target->getLastNonGroupParent())) { child->setRoute(QtLineItemBase::ROUTE_VERTICAL); @@ -194,7 +208,8 @@ void QtGraphEdge::updateLine() child->setEarlyBend(true); } } - else if (type != Edge::EDGE_AGGREGATION || owner != owner->getLastParent() || target != target->getLastParent()) + else if (type != Edge::EDGE_AGGREGATION || + owner != owner->getLastNonGroupParent() || target != target->getLastNonGroupParent()) { child->setRoute(QtLineItemBase::ROUTE_HORIZONTAL); } @@ -250,13 +265,18 @@ void QtGraphEdge::setIsFocused(bool isFocused) void QtGraphEdge::onClick() { - Edge::EdgeType type = (getData() ? getData()->getType() : Edge::EDGE_AGGREGATION); - if (!getData() || m_owner->isGroupNode() || m_target->isGroupNode()) { - QtGraphNode* node = - ((m_direction == TokenComponentAggregation::DIRECTION_BACKWARD) == (type != Edge::EDGE_INHERITANCE)) - ? m_owner : m_target; + QtGraphNode* node = (m_direction == TokenComponentAggregation::DIRECTION_BACKWARD ? m_owner : m_target); + if (m_owner->isGroupNode()) + { + node = m_owner; + } + else if (m_target->isGroupNode()) + { + node = m_target; + } + MessageGraphNodeBundleSplit(node->getTokenId()).dispatch(); } else if (isTrailEdge()) diff --git a/src/lib_gui/qt/view/graphElements/QtGraphNode.cpp b/src/lib_gui/qt/view/graphElements/QtGraphNode.cpp index 59bb187f..8c6d5faa 100644 --- a/src/lib_gui/qt/view/graphElements/QtGraphNode.cpp +++ b/src/lib_gui/qt/view/graphElements/QtGraphNode.cpp @@ -61,13 +61,13 @@ QtGraphNode* QtGraphNode::getParent() const return m_parentNode; } -QtGraphNode* QtGraphNode::getLastParent() const +QtGraphNode* QtGraphNode::getLastParent(bool noGroups) const { QtGraphNode* node = const_cast(this); while (true) { QtGraphNode* parent = dynamic_cast(node->parentItem()); - if (!parent) + if (!parent || (noGroups && parent->isGroupNode())) { break; } @@ -76,6 +76,11 @@ QtGraphNode* QtGraphNode::getLastParent() const return node; } +QtGraphNode* QtGraphNode::getLastNonGroupParent() const +{ + return getLastParent(true); +} + void QtGraphNode::setParent(QtGraphNode* parentNode) { m_parentNode = parentNode; @@ -86,7 +91,7 @@ void QtGraphNode::setParent(QtGraphNode* parentNode) } } -std::list QtGraphNode::getSubNodes() const +const std::list& QtGraphNode::getSubNodes() const { return m_subNodes; } @@ -142,11 +147,6 @@ Vec4i QtGraphNode::getBoundingRect() const return Vec4i(pos.x, pos.y, pos.x + size.x, pos.y + size.y); } -Vec4i QtGraphNode::getParentBoundingRect() const -{ - return getLastParent()->getBoundingRect(); -} - void QtGraphNode::addOutEdge(QtGraphEdge* edge) { m_outEdges.push_back(edge); @@ -348,7 +348,7 @@ void QtGraphNode::addSubNode(QtGraphNode* node) if (node->getIsActive()) { QtGraphNode* parent = this; - while (parent) + while (parent && !parent->isGroupNode()) { parent->setZValue(-10.0f); parent->m_text->setZValue(-9.0f); diff --git a/src/lib_gui/qt/view/graphElements/QtGraphNode.h b/src/lib_gui/qt/view/graphElements/QtGraphNode.h index e43934ab..161a68cb 100644 --- a/src/lib_gui/qt/view/graphElements/QtGraphNode.h +++ b/src/lib_gui/qt/view/graphElements/QtGraphNode.h @@ -37,10 +37,11 @@ public: virtual ~QtGraphNode(); QtGraphNode* getParent() const; - QtGraphNode* getLastParent() const; + QtGraphNode* getLastParent(bool noGroups = false) const; + QtGraphNode* getLastNonGroupParent() const; void setParent(QtGraphNode* parentNode); - std::list getSubNodes() const; + const std::list& getSubNodes() const; Vec2i getPosition() const; virtual bool setPosition(const Vec2i& position); @@ -52,7 +53,6 @@ public: void setSize(const QSize& size); Vec4i getBoundingRect() const; - Vec4i getParentBoundingRect() const; void addOutEdge(QtGraphEdge* edge); void addInEdge(QtGraphEdge* edge); diff --git a/src/lib_gui/qt/view/graphElements/QtGraphNodeData.cpp b/src/lib_gui/qt/view/graphElements/QtGraphNodeData.cpp index f03f19ae..abac6b99 100644 --- a/src/lib_gui/qt/view/graphElements/QtGraphNodeData.cpp +++ b/src/lib_gui/qt/view/graphElements/QtGraphNodeData.cpp @@ -55,11 +55,7 @@ void QtGraphNodeData::onClick() return; } - FilePath path = getFilePath(); - - MessageActivateNodes message; - message.addNode(m_data->getId(), path.empty() ? m_data->getNameHierarchy() : NameHierarchy(path.wstr(), NAME_DELIMITER_FILE)); - message.dispatch(); + MessageActivateNodes(m_data->getId()).dispatch(); } void QtGraphNodeData::moved(const Vec2i& oldPosition) diff --git a/src/lib_gui/qt/view/graphElements/QtGraphNodeGroup.cpp b/src/lib_gui/qt/view/graphElements/QtGraphNodeGroup.cpp index 442dc00b..8bac3e10 100644 --- a/src/lib_gui/qt/view/graphElements/QtGraphNodeGroup.cpp +++ b/src/lib_gui/qt/view/graphElements/QtGraphNodeGroup.cpp @@ -1,24 +1,33 @@ #include "qt/view/graphElements/QtGraphNodeGroup.h" #include -#include +#include +#include +#include #include +#include "utility/messaging/type/MessageActivateNodes.h" +#include "utility/messaging/type/MessageFocusIn.h" +#include "utility/messaging/type/MessageFocusOut.h" #include "utility/messaging/type/MessageGraphNodeBundleSplit.h" #include "qt/graphics/QtRoundedRectItem.h" -QtGraphNodeGroup::QtGraphNodeGroup(Id tokenId, const std::wstring& name, NodeType::GroupType type) +QtGraphNodeGroup::QtGraphNodeGroup( + Id tokenId, const std::wstring& name, GroupType type, bool interactive +) : m_tokenId(tokenId) , m_type(type) + , m_interactive(interactive) { - setAcceptHoverEvents(true); + if (interactive) + { + setAcceptHoverEvents(true); + } setName(name); - setZValue(-10.0f); - m_rect->setZValue(-10.0f); - if (type == NodeType::GROUP_FRAMELESS) + if (type == GroupType::FRAMELESS) { m_rect->hide(); return; @@ -29,25 +38,26 @@ QtGraphNodeGroup::QtGraphNodeGroup(Id tokenId, const std::wstring& name, NodeTyp return; } - m_background = new QtRoundedRectItem(this); - m_backgroundTopRight = new QGraphicsRectItem(this); - m_backgroundBottomLeft = new QGraphicsRectItem(this); - - m_background->setZValue(-10.0f); - m_backgroundTopRight->setZValue(-10.0f); - m_backgroundBottomLeft->setZValue(-10.0f); + m_background = new QGraphicsPolygonItem(this); + m_background->setZValue(-3.f); GraphViewStyle::NodeStyle style = GraphViewStyle::getStyleOfGroupNode(type, false); GraphViewStyle::NodeMargins margins = GraphViewStyle::getMarginsOfGroupNode(type, true); int width = style.textOffset.x * 2 + style.borderWidth + margins.charWidth * name.size(); - int height = margins.top * 2 + margins.charHeight; + int height = margins.spacingA + margins.charHeight; + int radius = style.cornerRadius; - m_background->setRadius(style.cornerRadius); - m_background->setRect(0, 0, width, height); + QPainterPath path; + path.moveTo(width, 0); + path.lineTo(radius, 0); + path.arcTo(0, 0, 2 * radius, 2 * radius, 90, 90); + path.lineTo(0, height); + path.lineTo(width - radius, height); + path.arcTo(width - 2 * radius, height - 2 * radius, 2 * radius, 2 * radius, 270, 90); + path.closeSubpath(); - m_backgroundTopRight->setRect(width - style.cornerRadius, 0, style.cornerRadius, style.cornerRadius); - m_backgroundBottomLeft->setRect(0, height - style.cornerRadius, style.cornerRadius, style.cornerRadius); + m_background->setPolygon(path.toFillPolygon()); } QtGraphNodeGroup::~QtGraphNodeGroup() @@ -66,24 +76,64 @@ Id QtGraphNodeGroup::getTokenId() const void QtGraphNodeGroup::onClick() { - MessageGraphNodeBundleSplit(m_tokenId).dispatch(); + if (!m_interactive || !m_isHovering) + { + return; + } + + if (m_type == GroupType::FILE || m_type == GroupType::NAMESPACE) + { + MessageActivateNodes(m_tokenId).dispatch(); + } + else + { + MessageGraphNodeBundleSplit(m_tokenId).dispatch(); + } } void QtGraphNodeGroup::updateStyle() { - GraphViewStyle::NodeStyle style = GraphViewStyle::getStyleOfGroupNode(m_type, false); + GraphViewStyle::NodeStyle style = GraphViewStyle::getStyleOfGroupNode(m_type, m_isHovering); if (m_background) { m_background->setBrush(QColor(style.color.border.c_str())); m_background->setPen(QPen(Qt::transparent)); - - m_backgroundTopRight->setBrush(QColor(style.color.border.c_str())); - m_backgroundTopRight->setPen(QPen(Qt::transparent)); - - m_backgroundBottomLeft->setBrush(QColor(style.color.border.c_str())); - m_backgroundBottomLeft->setPen(QPen(Qt::transparent)); } setStyle(style); } + +void QtGraphNodeGroup::hoverLeaveEvent(QGraphicsSceneHoverEvent* event) +{ + if (m_type == GroupType::FILE || m_type == GroupType::NAMESPACE) + { + MessageFocusOut({ m_tokenId }).dispatch(); + } + else + { + focusOut(); + } +} + +void QtGraphNodeGroup::hoverMoveEvent(QGraphicsSceneHoverEvent* event) +{ + if (!m_background || m_background->contains(event->pos())) + { + if (!m_isHovering) + { + if (m_type == GroupType::FILE || m_type == GroupType::NAMESPACE) + { + MessageFocusIn({ m_tokenId }, TOOLTIP_ORIGIN_GRAPH).dispatch(); + } + else + { + focusIn(); + } + } + } + else if (m_isHovering) + { + hoverLeaveEvent(nullptr); + } +} diff --git a/src/lib_gui/qt/view/graphElements/QtGraphNodeGroup.h b/src/lib_gui/qt/view/graphElements/QtGraphNodeGroup.h index 477a1af1..356fe03e 100644 --- a/src/lib_gui/qt/view/graphElements/QtGraphNodeGroup.h +++ b/src/lib_gui/qt/view/graphElements/QtGraphNodeGroup.h @@ -1,18 +1,17 @@ #ifndef QT_GRAPH_NODE_GROUP_H #define QT_GRAPH_NODE_GROUP_H -#include "data/NodeType.h" +#include "data/GroupType.h" #include "qt/view/graphElements/QtGraphNode.h" -class QGraphicsRectItem; -class QtRoundedRectItem; +class QGraphicsPolygonItem; class QtGraphNodeGroup : public QtGraphNode { Q_OBJECT public: - QtGraphNodeGroup(Id tokenId, const std::wstring& name, NodeType::GroupType type); + QtGraphNodeGroup(Id tokenId, const std::wstring& name, GroupType type, bool interactive); virtual ~QtGraphNodeGroup(); // QtGraphNode implementation @@ -22,13 +21,16 @@ public: virtual void onClick(); virtual void updateStyle(); +protected: + virtual void hoverLeaveEvent(QGraphicsSceneHoverEvent* event); + virtual void hoverMoveEvent(QGraphicsSceneHoverEvent* event); + private: Id m_tokenId; - NodeType::GroupType m_type; + GroupType m_type; + const bool m_interactive; - QtRoundedRectItem* m_background = nullptr; - QGraphicsRectItem* m_backgroundTopRight = nullptr; - QGraphicsRectItem* m_backgroundBottomLeft = nullptr; + QGraphicsPolygonItem* m_background = nullptr; }; #endif // QT_GRAPH_NODE_GROUP_H