diff --git a/bin/app/data/color_schemes/bad_rainbow.xml b/bin/app/data/color_schemes/bad_rainbow.xml index b99b01c6..8a69296c 100644 --- a/bin/app/data/color_schemes/bad_rainbow.xml +++ b/bin/app/data/color_schemes/bad_rainbow.xml @@ -403,6 +403,7 @@ #FBBA00 + #FFFFFF diff --git a/bin/app/data/color_schemes/bright.xml b/bin/app/data/color_schemes/bright.xml index c136a732..353274f2 100644 --- a/bin/app/data/color_schemes/bright.xml +++ b/bin/app/data/color_schemes/bright.xml @@ -371,6 +371,7 @@ #F4BC3D + #878787 diff --git a/bin/app/data/color_schemes/dark.xml b/bin/app/data/color_schemes/dark.xml index 3608f91f..626e9d0d 100644 --- a/bin/app/data/color_schemes/dark.xml +++ b/bin/app/data/color_schemes/dark.xml @@ -374,6 +374,7 @@ #9C8528 + #F7F7F7 diff --git a/src/lib/CMakeLists.txt b/src/lib/CMakeLists.txt index 4d3456c1..dd48db90 100644 --- a/src/lib/CMakeLists.txt +++ b/src/lib/CMakeLists.txt @@ -12,6 +12,8 @@ add_files( component/controller/helper/NetworkProtocolHelper.h component/controller/helper/SnippetMerger.cpp component/controller/helper/SnippetMerger.h + component/controller/helper/TrailLayouter.cpp + component/controller/helper/TrailLayouter.h component/controller/ActivationController.cpp component/controller/ActivationController.h @@ -225,7 +227,7 @@ add_files( data/TaskMergeStorages.h data/TaskShowStatusDialog.cpp data/TaskShowStatusDialog.h - + project/Project.cpp project/Project.h project/SourceGroupFactory.cpp @@ -234,7 +236,7 @@ add_files( project/SourceGroupFactoryModule.h project/SourceGroup.cpp project/SourceGroup.h - + settings/migration/Migration.cpp settings/migration/Migration.h settings/migration/MigrationDeleteKey.cpp @@ -245,7 +247,7 @@ add_files( settings/migration/MigrationMoveKey.h settings/migration/SettingsMigrator.cpp settings/migration/SettingsMigrator.h - + settings/ApplicationSettings.cpp settings/ApplicationSettings.h settings/ColorScheme.cpp @@ -327,6 +329,7 @@ add_files( utility/messaging/type/MessageActivateSourceLocations.h utility/messaging/type/MessageActivateTokenIds.h utility/messaging/type/MessageActivateTokens.h + utility/messaging/type/MessageActivateTrail.h utility/messaging/type/MessageActivateWindow.h utility/messaging/type/MessageChangeFileView.h utility/messaging/type/MessageClearErrorCount.h diff --git a/src/lib/component/controller/GraphController.cpp b/src/lib/component/controller/GraphController.cpp index 7e8a71e1..612b332c 100644 --- a/src/lib/component/controller/GraphController.cpp +++ b/src/lib/component/controller/GraphController.cpp @@ -9,6 +9,7 @@ #include "component/controller/helper/BucketLayouter.h" #include "component/controller/helper/ListLayouter.h" +#include "component/controller/helper/TrailLayouter.h" #include "component/view/GraphView.h" #include "component/view/GraphViewStyle.h" #include "data/access/StorageAccess.h" @@ -42,7 +43,7 @@ void GraphController::handleMessage(MessageActivateAll* message) assignBundleIds(); layoutGraph(); - buildGraph(message, false); + buildGraph(message, false, true, false); } void GraphController::handleMessage(MessageActivateTokens* message) @@ -53,7 +54,7 @@ void GraphController::handleMessage(MessageActivateTokens* message) { m_activeEdgeIds = message->tokenIds; setActiveAndVisibility(utility::concat(m_activeNodeIds, m_activeEdgeIds)); - buildGraph(message, false, false); + buildGraph(message, false, false, false); return; } else if (message->isAggregation) @@ -101,9 +102,55 @@ void GraphController::handleMessage(MessageActivateTokens* message) buildGraph(message, !isNamespace, true, isNamespace); } +void GraphController::handleMessage(MessageActivateTrail* message) +{ + TRACE("trail activate"); + + m_activeEdgeIds.clear(); + + std::shared_ptr graph = m_storageAccess->getGraphForTrail( + message->originId, message->targetId, message->trailType, message->depth); + + createDummyGraphForTokenIds(m_activeNodeIds, graph); + m_graph->setTrailMode(message->horizontalLayout ? Graph::TRAIL_HORIZONTAL : Graph::TRAIL_VERTICAL); + + setVisibility(setActive(m_activeNodeIds, true)); + + layoutNesting(); + + TrailLayouter::LayoutDirection direction; + if (message->horizontalLayout) + { + if (message->originId) + { + direction = TrailLayouter::LAYOUT_LEFT_RIGHT; + } + else + { + direction = TrailLayouter::LAYOUT_RIGHT_LEFT; + } + } + else + { + if (message->originId) + { + direction = TrailLayouter::LAYOUT_TOP_BOTTOM; + } + else + { + direction = TrailLayouter::LAYOUT_BOTTOM_TOP; + } + } + + TrailLayouter layout(direction); + layout.layoutGraph(m_dummyNodes, m_dummyEdges, m_topLevelAncestorIds); + + buildGraph(message, true, true, false); +} + void GraphController::handleMessage(MessageFlushUpdates* message) { - buildGraph(message, true, !message->keepContent()); + buildGraph(message, true, !message->keepContent(), false); } void GraphController::handleMessage(MessageScrollGraph* message) @@ -228,7 +275,7 @@ void GraphController::handleMessage(MessageGraphNodeExpand* message) layoutNesting(); layoutGraph(); - buildGraph(message, false); + buildGraph(message, false, true, false); } } @@ -241,7 +288,7 @@ void GraphController::handleMessage(MessageGraphNodeMove* message) if (message->isReplayed()) { - buildGraph(message, false); + buildGraph(message, false, true, false); } else { @@ -264,7 +311,7 @@ void GraphController::handleMessage(MessageShowReference* message) m_activeEdgeIds = std::vector(1, message->tokenId); setActiveAndVisibility(utility::concat(m_activeNodeIds, m_activeEdgeIds)); - buildGraph(message, false, false); + buildGraph(message, false, false, false); } GraphView* GraphController::getView() const @@ -300,6 +347,7 @@ void GraphController::createDummyGraphForTokenIds(const std::vector& tokenId m_dummyEdges.clear(); m_dummyGraphNodes.clear(); + m_topLevelAncestorIds.clear(); std::set addedNodes; std::vector> dummyNodes; @@ -308,14 +356,14 @@ void GraphController::createDummyGraphForTokenIds(const std::vector& tokenId [&addedNodes, &dummyNodes, this](Node* node) { Node* parent = node->getLastParentNode(); - Id id = parent->getId(); - if (addedNodes.find(id) != addedNodes.end()) + Id parentId = parent->getId(); + if (addedNodes.find(parentId) != addedNodes.end()) { return; } - addedNodes.insert(id); + addedNodes.insert(parentId); - utility::append(dummyNodes, createDummyNodeTopDown(parent)); + utility::append(dummyNodes, createDummyNodeTopDown(parent, parentId)); } ); @@ -370,7 +418,7 @@ void GraphController::createDummyGraphForTokenIdsAndSetActiveAndVisibility( createDummyGraphForTokenIds(tokenIds, graph); - bool noActive = setActive(tokenIds); + bool noActive = setActive(tokenIds, false); autoExpandActiveNode(tokenIds); setExpandedNodeIds(expandedNodeIds); @@ -378,24 +426,26 @@ void GraphController::createDummyGraphForTokenIdsAndSetActiveAndVisibility( setVisibility(noActive); } -std::vector> GraphController::createDummyNodeTopDown(Node* node) +std::vector> GraphController::createDummyNodeTopDown(Node* node, Id ancestorId) { std::vector> nodes; std::shared_ptr result = std::make_shared(); result->data = node; - result->tokenId = node->getId(); result->name = node->getName(); + result->tokenId = node->getId(); + m_topLevelAncestorIds.emplace(node->getId(), ancestorId); + m_dummyGraphNodes.emplace(result->data->getId(), result); nodes.push_back(result); if (node->isType(Node::NODE_NAMESPACE)) { node->forEachChildNode( - [&nodes, this](Node* child) + [&nodes, &ancestorId, this](Node* child) { - utility::append(nodes, createDummyNodeTopDown(child)); + utility::append(nodes, createDummyNodeTopDown(child, ancestorId)); } ); @@ -403,7 +453,7 @@ std::vector> GraphController::createDummyNodeTopDown( } node->forEachChildNode( - [node, &result, this](Node* child) + [node, &result, &ancestorId, this](Node* child) { DummyNode* parent = nullptr; AccessKind accessKind = ACCESS_NONE; @@ -432,7 +482,7 @@ std::vector> GraphController::createDummyNodeTopDown( parent = accessNode.get(); } - utility::append(parent->subNodes, createDummyNodeTopDown(child)); + utility::append(parent->subNodes, createDummyNodeTopDown(child, ancestorId)); } ); @@ -482,7 +532,7 @@ void GraphController::autoExpandActiveNode(const std::vector& activeTokenIds } } -bool GraphController::setActive(const std::vector& activeTokenIds) +bool GraphController::setActive(const std::vector& activeTokenIds, bool showAllEdges) { TRACE(); @@ -513,7 +563,7 @@ bool GraphController::setActive(const std::vector& activeTokenIds) DummyNode* from = getDummyGraphNodeById(edge->ownerId); DummyNode* to = getDummyGraphNodeById(edge->targetId); - if (from && to && (noActive || from->active || to->active || edge->active)) + if (from && to && (showAllEdges || noActive || from->active || to->active || edge->active)) { edge->visible = true; from->connected = true; @@ -540,7 +590,7 @@ void GraphController::setActiveAndVisibility(const std::vector& activeTokenI { TRACE(); - setVisibility(setActive(activeTokenIds)); + setVisibility(setActive(activeTokenIds, false)); } void GraphController::setNodeActiveRecursive(DummyNode* node, const std::vector& activeTokenIds, bool* noActive) const @@ -1468,7 +1518,8 @@ DummyNode* GraphController::getDummyGraphNodeById(Id tokenId) const return nullptr; } -void GraphController::buildGraph(MessageBase* message, bool centerActiveNode, bool animatedTransition, bool scrollToTop) +void GraphController::buildGraph( + MessageBase* message, bool centerActiveNode, bool animatedTransition, bool scrollToTop) { if (!message->isReplayed()) { @@ -1742,7 +1793,7 @@ void GraphController::handleMessage(MessageColorSchemeTest* message) } ); - setActive(activeTokenIds); + setActive(activeTokenIds, true); layoutNesting(); @@ -1758,7 +1809,7 @@ void GraphController::handleMessage(MessageColorSchemeTest* message) grid.createBuckets(m_dummyNodes, std::vector>()); grid.layoutBuckets(); - buildGraph(message, false); + buildGraph(message, false, true, false); getView()->focusTokenIds(focusedTokenIds); } diff --git a/src/lib/component/controller/GraphController.h b/src/lib/component/controller/GraphController.h index 7384dc87..6596effd 100644 --- a/src/lib/component/controller/GraphController.h +++ b/src/lib/component/controller/GraphController.h @@ -7,6 +7,7 @@ #include "utility/messaging/MessageListener.h" #include "utility/messaging/type/MessageActivateAll.h" #include "utility/messaging/type/MessageActivateTokens.h" +#include "utility/messaging/type/MessageActivateTrail.h" #include "utility/messaging/type/MessageColorSchemeTest.h" #include "utility/messaging/type/MessageFlushUpdates.h" #include "utility/messaging/type/MessageFocusIn.h" @@ -34,6 +35,7 @@ class GraphController : public Controller , public MessageListener , public MessageListener + , public MessageListener , public MessageListener , public MessageListener , public MessageListener @@ -53,6 +55,7 @@ public: private: virtual void handleMessage(MessageActivateAll* message); virtual void handleMessage(MessageActivateTokens* message); + virtual void handleMessage(MessageActivateTrail* message); virtual void handleMessage(MessageFlushUpdates* message); virtual void handleMessage(MessageFocusIn* message); virtual void handleMessage(MessageFocusOut* message); @@ -71,13 +74,13 @@ private: void createDummyGraphForTokenIds(const std::vector& tokenIds, const std::shared_ptr graph); void createDummyGraphForTokenIdsAndSetActiveAndVisibility( const std::vector& tokenIds, const std::shared_ptr graph); - std::vector> createDummyNodeTopDown(Node* node); + std::vector> createDummyNodeTopDown(Node* node, Id ancestorId); std::vector getExpandedNodeIds() const; void setExpandedNodeIds(const std::vector& nodeIds); void autoExpandActiveNode(const std::vector& activeTokenIds); - bool setActive(const std::vector& activeTokenIds); + bool setActive(const std::vector& activeTokenIds, bool showAllEdges); void setVisibility(bool noActive); void setActiveAndVisibility(const std::vector& activeTokenIds); void setNodeActiveRecursive(DummyNode* node, const std::vector& activeTokenIds, bool* noActive) const; @@ -108,7 +111,7 @@ private: DummyNode* getDummyGraphNodeById(Id tokenId) const; - void buildGraph(MessageBase* message, bool centerActiveNode, bool animatedTransition = true, bool scrollToTop = false); + void buildGraph(MessageBase* message, bool centerActiveNode, bool animatedTransition, bool scrollToTop); void forEachDummyNodeRecursive(std::function func); void forEachDummyEdge(std::function func); @@ -126,6 +129,8 @@ private: std::vector m_activeEdgeIds; std::shared_ptr m_graph; + + std::map m_topLevelAncestorIds; }; #endif // GRAPH_CONTROLLER_H diff --git a/src/lib/component/controller/UndoRedoController.cpp b/src/lib/component/controller/UndoRedoController.cpp index abba420a..d2c85ef3 100644 --- a/src/lib/component/controller/UndoRedoController.cpp +++ b/src/lib/component/controller/UndoRedoController.cpp @@ -77,6 +77,19 @@ void UndoRedoController::handleMessage(MessageActivateTokens* message) processCommand(command); } +void UndoRedoController::handleMessage(MessageActivateTrail* message) +{ + if (sameMessageTypeAsLast(message) && + static_cast(lastMessage())->originId == message->originId && + static_cast(lastMessage())->targetId == message->targetId) + { + return; + } + + Command command(std::make_shared(*message), Command::ORDER_ADAPT, true); + processCommand(command); +} + void UndoRedoController::handleMessage(MessageChangeFileView* message) { Command command(std::make_shared(*message), Command::ORDER_VIEW); @@ -320,9 +333,21 @@ void UndoRedoController::replayCommands(std::list::iterator it) std::vector::iterator> viewCommands; bool keepsContent = true; + std::map::iterator> lastOfType; + std::list::iterator at = it; + while (at != m_iterator) + { + if (at->replayLastOnly) + { + lastOfType[at->message->getType()] = at; + } + + std::advance(at, 1); + } + while (it != m_iterator) { - if (it->order != Command::ORDER_VIEW || it->replayLastOnly == false) + if (!it->replayLastOnly || lastOfType[it->message->getType()] == it) { replayCommand(it); @@ -330,13 +355,8 @@ void UndoRedoController::replayCommands(std::list::iterator it) { keepsContent = false; } - - if (it->order != Command::ORDER_VIEW) - { - viewCommands.clear(); - } } - else + else if (it->order == Command::ORDER_VIEW) { viewCommands.push_back(it); } diff --git a/src/lib/component/controller/UndoRedoController.h b/src/lib/component/controller/UndoRedoController.h index 815e466d..9e806725 100644 --- a/src/lib/component/controller/UndoRedoController.h +++ b/src/lib/component/controller/UndoRedoController.h @@ -8,6 +8,7 @@ #include "utility/messaging/type/MessageActivateAll.h" #include "utility/messaging/type/MessageActivateLocalSymbols.h" #include "utility/messaging/type/MessageActivateTokens.h" +#include "utility/messaging/type/MessageActivateTrail.h" #include "utility/messaging/type/MessageChangeFileView.h" #include "utility/messaging/type/MessageDeactivateEdge.h" #include "utility/messaging/type/MessageFinishedParsing.h" @@ -34,6 +35,7 @@ class UndoRedoController , public MessageListener , public MessageListener , public MessageListener + , public MessageListener , public MessageListener , public MessageListener , public MessageListener @@ -78,6 +80,7 @@ private: virtual void handleMessage(MessageActivateAll* message); virtual void handleMessage(MessageActivateLocalSymbols* message); virtual void handleMessage(MessageActivateTokens* message); + virtual void handleMessage(MessageActivateTrail* message); virtual void handleMessage(MessageChangeFileView* message); virtual void handleMessage(MessageDeactivateEdge* message); virtual void handleMessage(MessageFinishedParsing* message); diff --git a/src/lib/component/controller/helper/BucketLayouter.cpp b/src/lib/component/controller/helper/BucketLayouter.cpp index e40e9e47..614a7f02 100644 --- a/src/lib/component/controller/helper/BucketLayouter.cpp +++ b/src/lib/component/controller/helper/BucketLayouter.cpp @@ -154,7 +154,7 @@ void BucketLayouter::createBuckets( std::shared_ptr target = findTopMostDummyNodeRecursive(nodes, edge->targetId, nullptr); bool removeEdge = false; - if (!owner || !target) + if (!owner || !target || owner == target) { removeEdge = true; } diff --git a/src/lib/component/controller/helper/DummyEdge.h b/src/lib/component/controller/helper/DummyEdge.h index 90f0a67d..ef5075b0 100644 --- a/src/lib/component/controller/helper/DummyEdge.h +++ b/src/lib/component/controller/helper/DummyEdge.h @@ -2,6 +2,7 @@ #define DUMMY_EDGE_H #include "utility/types.h" +#include "utility/math/Vector4.h" #include "data/graph/Edge.h" #include "data/graph/token_component/TokenComponentAggregation.h" @@ -86,6 +87,8 @@ struct DummyEdge bool visible; bool active; + std::vector path; + // BundleEdge int weight; TokenComponentAggregation::Direction direction; diff --git a/src/lib/component/controller/helper/DummyNode.h b/src/lib/component/controller/helper/DummyNode.h index c1dd048f..60b24f14 100644 --- a/src/lib/component/controller/helper/DummyNode.h +++ b/src/lib/component/controller/helper/DummyNode.h @@ -53,7 +53,6 @@ public: DummyNode() : visible(false) , childVisible(false) - , topLevelAncestorId(0) , tokenId(0) , data(nullptr) , active(false) @@ -244,7 +243,6 @@ public: bool visible; bool childVisible; - Id topLevelAncestorId; Id tokenId; std::vector> subNodes; diff --git a/src/lib/component/controller/helper/TrailLayouter.cpp b/src/lib/component/controller/helper/TrailLayouter.cpp new file mode 100644 index 00000000..db0cfc73 --- /dev/null +++ b/src/lib/component/controller/helper/TrailLayouter.cpp @@ -0,0 +1,573 @@ +#include "component/controller/helper/TrailLayouter.h" + +#include + +TrailLayouter::TrailLayouter(LayoutDirection dir) + : m_direction(dir) +{ +} + +void TrailLayouter::layoutGraph( + std::vector>& dummyNodes, + const std::vector>& dummyEdges, + const std::map& topLevelAncestorIds) +{ + buildGraph(dummyNodes, dummyEdges, topLevelAncestorIds); + + makeAcyclic(); + + assignLongestPathLevels(); + assignRemainingLevels(); + + addVirtualNodes(); + + buildColumns(); + reduceEdgeCrossings(); + layout(); + + retrievePositions(topLevelAncestorIds); + + // print(); +} + +void TrailLayouter::buildGraph( + std::vector>& dummyNodes, + const std::vector>& dummyEdges, + const std::map& topLevelAncestorIds) +{ + for (const std::shared_ptr dummyNode : dummyNodes) + { + addNode(dummyNode); + } + + for (const std::shared_ptr dummyEdge : dummyEdges) + { + dummyEdge->path.clear(); + + if (dummyEdge->data && !dummyEdge->data->isType(Edge::EDGE_OVERRIDE | Edge::EDGE_INHERITANCE)) + { + addEdge(dummyEdge, topLevelAncestorIds); + } + } + + for (const std::shared_ptr dummyEdge : dummyEdges) + { + if (dummyEdge->data && dummyEdge->data->isType(Edge::EDGE_OVERRIDE | Edge::EDGE_INHERITANCE)) + { + addEdge(dummyEdge, topLevelAncestorIds); + } + } +} + +void TrailLayouter::makeAcyclic() +{ + std::set edgesToSwitch; + for (std::shared_ptr node : m_allNodes) + { + if (!node->incomingEdges.size()) + { + utility::append(edgesToSwitch, node->outgoingEdges); + } + } + + for (TrailEdge* edge : edgesToSwitch) + { + switchEdge(edge); + } + + for (TrailNode* node : m_rootNodes) + { + std::set edgesToSwitch = node->incomingEdges; + for (TrailEdge* edge : edgesToSwitch) + { + switchEdge(edge); + } + + makeAcyclicRecursive(node, std::set()); + } +} + +void TrailLayouter::makeAcyclicRecursive(TrailNode* node, std::set predecessors) +{ + predecessors.insert(node); + + std::set edgesToSwitch; + for (TrailEdge* edge : node->outgoingEdges) + { + if (predecessors.find(edge->target) != predecessors.end()) + { + edgesToSwitch.insert(edge); + } + else + { + makeAcyclicRecursive(edge->target, predecessors); + } + } + + for (TrailEdge* edge : edgesToSwitch) + { + switchEdge(edge); + } +} + +void TrailLayouter::assignLongestPathLevels() +{ + std::set nodes(m_rootNodes.begin(), m_rootNodes.end()); + std::map predecessorNodes; + + int level = 0; + + while (nodes.size()) + { + std::set newNodes; + + for (TrailNode* node : nodes) + { + for (TrailEdge* edge : node->outgoingEdges) + { + newNodes.insert(edge->target); + predecessorNodes[edge->target] = node; + } + } + + if (!newNodes.size()) + { + break; + } + + nodes = newNodes; + level++; + } + + while (nodes.size()) + { + std::set newNodes; + + for (TrailNode* node : nodes) + { + node->level = level; + + if (level > 0) + { + newNodes.insert(predecessorNodes[node]); + } + } + + nodes = newNodes; + level--; + } +} + +void TrailLayouter::assignRemainingLevels() +{ + std::set nodes(m_rootNodes.begin(), m_rootNodes.end()); + while (nodes.size()) + { + std::set newNodes; + + for (TrailNode* node : nodes) + { + for (TrailEdge* edge : node->outgoingEdges) + { + newNodes.insert(edge->target); + } + + if (node->level < 0) + { + int level = -1; + + for (TrailEdge* edge : node->incomingEdges) + { + level = std::max(level, edge->origin->level + 1); + } + + node->level = level; + } + } + + nodes = newNodes; + } +} + +void TrailLayouter::addVirtualNodes() +{ + std::vector> newEdges; + + for (std::shared_ptr edge : m_allEdges) + { + for (int i = edge->origin->level + 1; i < edge->target->level; i++) + { + std::shared_ptr virtualNode = std::make_shared(); + virtualNode->id = 0; + virtualNode->name = ""; + virtualNode->dummyNode = nullptr; + virtualNode->level = i; + + virtualNode->size = Vec2i(50, 20); + + m_allNodes.push_back(virtualNode); + edge->virtualNodes.push_back(virtualNode.get()); + + + std::shared_ptr virtualEdge = std::make_shared(); + virtualEdge->id = 0; + + virtualEdge->origin = edge->origin; + virtualEdge->origin->outgoingEdges.erase(edge.get()); + virtualEdge->origin->outgoingEdges.insert(virtualEdge.get()); + + virtualEdge->target = virtualNode.get(); + virtualEdge->target->incomingEdges.insert(virtualEdge.get()); + virtualEdge->target->outgoingEdges.insert(edge.get()); + + edge->origin = virtualNode.get(); + newEdges.push_back(virtualEdge); + } + } + + m_allEdges.insert(m_allEdges.end(), newEdges.begin(), newEdges.end()); +} + +void TrailLayouter::buildColumns() +{ + for (std::shared_ptr node : m_allNodes) + { + int level = node->level + 1; + for (int i = m_nodesPerCol.size(); i <= level; i++) + { + m_nodesPerCol.push_back(std::vector()); + } + + m_nodesPerCol[level].push_back(node.get()); + } +} + +void TrailLayouter::reduceEdgeCrossings() +{ + for (size_t i = 1; i < m_nodesPerCol.size(); i++) + { + std::vector nodes = m_nodesPerCol[i]; + + bool usePredecessors = true; + std::vector neighbors = m_nodesPerCol[i - 1]; + if (neighbors.size() == 1 && i + 1 < m_nodesPerCol.size() && m_nodesPerCol[i + 1].size() > 0) + { + neighbors = m_nodesPerCol[i + 1]; + usePredecessors = false; + } + + std::map neighborIndices; + for (size_t j = 0; j < neighbors.size(); j++) + { + neighborIndices.emplace(neighbors[j], j); + } + + std::multimap newOrder; + + for (size_t j = 0; j < nodes.size(); j++) + { + TrailNode* node = nodes[j]; + + size_t sum = 0; + size_t count = 0; + + if (usePredecessors) + { + for (TrailEdge* edge : node->incomingEdges) + { + sum += neighborIndices[edge->origin]; + count++; + } + } + else + { + for (TrailEdge* edge : node->outgoingEdges) + { + sum += neighborIndices[edge->target]; + count++; + } + } + + float value = j; + if (count) + { + value = float(sum) / count; + } + newOrder.emplace(value, node); + } + + nodes.clear(); + for (std::pair p : newOrder) + { + nodes.push_back(p.second); + } + m_nodesPerCol[i] = nodes; + } +} + +void TrailLayouter::layout() +{ + // calculate widths and heights of columns, and find largest column + std::vector widthsPerCol; + std::vector heightsPerCol; + + int maxHeight = 0; + size_t maxHeightIndex = 0; + + unsigned int xIdx = horizontalLayout() ? 0 : 1; + unsigned int yIdx = horizontalLayout() ? 1 : 0; + + for (size_t i = 0; i < m_nodesPerCol.size(); i++) + { + std::vector& nodes = m_nodesPerCol[i]; + + int width = 0; + int height = -30; + + for (TrailNode* node : nodes) + { + height += node->size.getValue(yIdx) + 30; + width = std::max(width, node->size.getValue(xIdx)); + } + + widthsPerCol.push_back(width); + heightsPerCol.push_back(height); + + if (height > maxHeight) + { + maxHeight = height; + maxHeightIndex = i; + } + } + + // layout nodes within columns + int x = 0; + for (size_t i = 0; i < m_nodesPerCol.size(); i++) + { + std::vector& nodes = m_nodesPerCol[i]; + int y = -heightsPerCol[i] / 2; + + for (TrailNode* node : nodes) + { + node->pos = horizontalLayout() ? Vec2i(x, y) : Vec2i(y, x); + y += node->size.getValue(yIdx) + 30; + + if (!node->id) + { + node->size.setValue(xIdx, widthsPerCol[i]); + } + } + + if (i + 1 == m_nodesPerCol.size()) + { + continue; + } + + if (invertedLayout()) + { + x -= widthsPerCol[i + 1] + 150; + } + else + { + x += widthsPerCol[i] + 150; + } + } + + // process layout before highest column + for (size_t i = maxHeightIndex; i > 0; i--) + { + moveNodesToAveragePosition(m_nodesPerCol[i - 1]); + } + + // process layout after highest column + for (size_t i = maxHeightIndex + 1; i < m_nodesPerCol.size(); i++) + { + moveNodesToAveragePosition(m_nodesPerCol[i]); + } + + // put into grid +} + +void TrailLayouter::moveNodesToAveragePosition(std::vector nodes) +{ + unsigned int yIdx = horizontalLayout() ? 1 : 0; + + for (size_t k = 0; k < 2; k++) + { + for (size_t j = 0; j < nodes.size(); j++) + { + size_t l = k ? nodes.size() - 1 - j : j; + size_t i = l % 2 ? nodes.size() - (l + 1) / 2 : l / 2; + TrailNode* node = nodes[i]; + + int sum = 0; + int count = 0; + + for (TrailEdge* edge : node->outgoingEdges) + { + sum += edge->target->pos.getValue(yIdx) + edge->target->size.getValue(yIdx) / 2; + count++; + } + + for (TrailEdge* edge : node->incomingEdges) + { + sum += edge->origin->pos.getValue(yIdx) + edge->origin->size.getValue(yIdx) / 2; + count++; + } + + if (count) + { + node->pos.setValue(yIdx, sum / count - node->size.getValue(yIdx) / 2); + + TrailNode* above = i > 0 ? nodes[i - 1] : nullptr; + if (above && above->pos.getValue(yIdx) + above->size.getValue(yIdx) + 30 > node->pos.getValue(yIdx)) + { + node->pos.setValue(yIdx, above->pos.getValue(yIdx) + above->size.getValue(yIdx) + 30); + } + + TrailNode* below = i + 1 < nodes.size() ? nodes[i + 1] : nullptr; + if (below && below->pos.getValue(yIdx) - 30 < node->pos.getValue(yIdx) + node->size.getValue(yIdx)) + { + node->pos.setValue(yIdx, below->pos.getValue(yIdx) - 30 - node->size.getValue(yIdx)); + } + } + } + } +} + +void TrailLayouter::retrievePositions(const std::map& topLevelAncestorIds) +{ + for (std::shared_ptr node : m_allNodes) + { + if (node->dummyNode) + { + node->dummyNode->position = node->pos; + } + } + + for (std::shared_ptr edge : m_allEdges) + { + if (edge->virtualNodes.size()) + { + for (DummyEdge* dummyEdge : edge->dummyEdges) + { + bool forward = edge->target->id == topLevelAncestorIds.find(dummyEdge->targetId)->second; + for (size_t i = 0; i < edge->virtualNodes.size(); i++) + { + TrailNode* node = edge->virtualNodes[forward ? i : edge->virtualNodes.size() - 1 - i]; + dummyEdge->path.push_back( + Vec4i(node->pos.x, node->pos.y, node->pos.x + node->size.x, node->pos.y + node->size.y)); + } + } + } + } +} + +void TrailLayouter::print() +{ + std::cout << "graph: " << std::endl; + for (std::shared_ptr node : m_allNodes) + { + std::cout << node->id << "\t" << node->level << "\t"; + std::cout << node->incomingEdges.size() << "\t" << node->outgoingEdges.size() << "\t"; + std::cout << node->name << std::endl; + } + std::cout << std::endl; + + for (std::shared_ptr edge : m_allEdges) + { + std::cout << edge->id << "\t" << edge->origin->name << "\t" << edge->target->name << std::endl; + } + std::cout << std::endl; +} + +void TrailLayouter::addNode(const std::shared_ptr& dummyNode) +{ + std::shared_ptr node = std::make_shared(); + node->id = dummyNode->tokenId; + node->name = dummyNode->name; + node->dummyNode = dummyNode.get(); + node->level = -1; + + node->size = dummyNode->size; + + m_allNodes.push_back(node); + + if (node->id) + { + m_nodesById.emplace(node->id, node.get()); + } + + if (dummyNode->hasActiveSubNode()) + { + m_rootNodes.push_back(node.get()); + } +} + +void TrailLayouter::addEdge(const std::shared_ptr dummyEdge, const std::map& topLevelAncestorIds) +{ + std::shared_ptr edge = std::make_shared(); + if (!dummyEdge->data) + { + return; + } + + edge->id = dummyEdge->data->getId(); + + Id originTopLevelId = topLevelAncestorIds.find(dummyEdge->ownerId)->second; + Id targetTopLevelId = topLevelAncestorIds.find(dummyEdge->targetId)->second; + + if (dummyEdge->data->isType(Edge::EDGE_OVERRIDE | Edge::EDGE_INHERITANCE) != invertedLayout()) + { + std::swap(originTopLevelId, targetTopLevelId); + } + + auto origin = m_nodesById.find(originTopLevelId); + auto target = m_nodesById.find(targetTopLevelId); + + if (origin == m_nodesById.end() || target == m_nodesById.end() || origin == target) + { + return; + } + + edge->origin = origin->second; + edge->target = target->second; + + for (std::shared_ptr e : m_allEdges) + { + if ((e->origin == edge->origin && e->target == edge->target) || + (e->origin == edge->target && e->target == edge->origin)) + { + e->dummyEdges.push_back(dummyEdge.get()); + return; + } + } + + edge->dummyEdges.push_back(dummyEdge.get()); + + edge->origin->outgoingEdges.insert(edge.get()); + edge->target->incomingEdges.insert(edge.get()); + + m_allEdges.push_back(edge); +} + +void TrailLayouter::switchEdge(TrailEdge* edge) +{ + edge->origin->outgoingEdges.erase(edge); + edge->origin->incomingEdges.insert(edge); + + edge->target->incomingEdges.erase(edge); + edge->target->outgoingEdges.insert(edge); + + std::swap(edge->origin, edge->target); +} + +bool TrailLayouter::horizontalLayout() const +{ + return m_direction == LAYOUT_LEFT_RIGHT || m_direction == LAYOUT_RIGHT_LEFT; +} + +bool TrailLayouter::invertedLayout() const +{ + return m_direction == LAYOUT_RIGHT_LEFT || m_direction == LAYOUT_BOTTOM_TOP; +} diff --git a/src/lib/component/controller/helper/TrailLayouter.h b/src/lib/component/controller/helper/TrailLayouter.h new file mode 100644 index 00000000..89dd6fd6 --- /dev/null +++ b/src/lib/component/controller/helper/TrailLayouter.h @@ -0,0 +1,99 @@ +#ifndef GRAPH_LAYOUTER_H +#define GRAPH_LAYOUTER_H + +#include +#include +#include + +#include "component/controller/helper/DummyEdge.h" +#include "component/controller/helper/DummyNode.h" + +// based on Sugiyama dependency graph layouting + +class TrailLayouter +{ +public: + enum LayoutDirection + { + LAYOUT_LEFT_RIGHT, + LAYOUT_RIGHT_LEFT, + LAYOUT_TOP_BOTTOM, + LAYOUT_BOTTOM_TOP + }; + + TrailLayouter(LayoutDirection dir); + + void layoutGraph( + std::vector>& dummyNodes, + const std::vector>& dummyEdges, + const std::map& topLevelAncestorIds); + +private: + struct TrailEdge; + + struct TrailNode + { + Id id; + int level; + std::string name; + + Vec2i pos; + Vec2i size; + + std::set incomingEdges; + std::set outgoingEdges; + + DummyNode* dummyNode; + }; + + struct TrailEdge + { + Id id; + TrailNode* origin; + TrailNode* target; + + std::vector virtualNodes; + + std::vector dummyEdges; + }; + + void buildGraph( + std::vector>& dummyNodes, + const std::vector>& dummyEdges, + const std::map& topLevelAncestorIds); + + void makeAcyclic(); + void makeAcyclicRecursive(TrailNode* node, std::set predecessors); + + void assignLongestPathLevels(); + void assignRemainingLevels(); + + void addVirtualNodes(); + void buildColumns(); + void reduceEdgeCrossings(); + + void layout(); + void moveNodesToAveragePosition(std::vector nodes); + void retrievePositions(const std::map& topLevelAncestorIds); + + void print(); + + void addNode(const std::shared_ptr& dummyNode); + void addEdge(const std::shared_ptr dummyEdge, const std::map& topLevelAncestorIds); + void switchEdge(TrailEdge* edge); + + bool horizontalLayout() const; + bool invertedLayout() const; + + LayoutDirection m_direction; + + std::vector> m_allNodes; + std::vector> m_allEdges; + + std::map m_nodesById; + std::vector m_rootNodes; + + std::vector> m_nodesPerCol; +}; + +#endif // GRAPH_LAYOUTER_H diff --git a/src/lib/component/view/GraphViewStyle.cpp b/src/lib/component/view/GraphViewStyle.cpp index 245d724c..c400b620 100644 --- a/src/lib/component/view/GraphViewStyle.cpp +++ b/src/lib/component/view/GraphViewStyle.cpp @@ -99,7 +99,6 @@ GraphViewStyle::NodeStyle::NodeStyle() GraphViewStyle::EdgeStyle::EdgeStyle() : width(0) , zValue(0) - , isStraight(false) , arrowLength(0) , arrowWidth(0) , arrowClosed(false) @@ -550,7 +549,8 @@ GraphViewStyle::NodeStyle GraphViewStyle::getStyleOfTextNode() return style; } -GraphViewStyle::EdgeStyle GraphViewStyle::getStyleForEdgeType(Edge::EdgeType type, bool isActive, bool isFocused) +GraphViewStyle::EdgeStyle GraphViewStyle::getStyleForEdgeType( + Edge::EdgeType type, bool isActive, bool isFocused, bool isTrailEdge) { EdgeStyle style; @@ -588,6 +588,13 @@ GraphViewStyle::EdgeStyle GraphViewStyle::getStyleForEdgeType(Edge::EdgeType typ style.originOffset.y = 1; style.targetOffset.y = -1; style.verticalOffset = 4; + + if (isTrailEdge && isActive) + { + style.width = 3; + style.color = ColorScheme::getInstance()->getColor( + "graph/edge/" + Edge::getUnderscoredTypeString(type) + "/trail_focus", style.color); + } break; case Edge::EDGE_USAGE: style.originOffset.y = 3; @@ -605,6 +612,11 @@ GraphViewStyle::EdgeStyle GraphViewStyle::getStyleForEdgeType(Edge::EdgeType typ break; } + if (isTrailEdge) + { + style.zValue = isActive ? 5 : 2; + } + return style; } diff --git a/src/lib/component/view/GraphViewStyle.h b/src/lib/component/view/GraphViewStyle.h index 73835b32..0157aabd 100644 --- a/src/lib/component/view/GraphViewStyle.h +++ b/src/lib/component/view/GraphViewStyle.h @@ -80,8 +80,6 @@ public: float width; int zValue; - bool isStraight; - int arrowLength; int arrowWidth; bool arrowClosed; @@ -128,7 +126,7 @@ public: static NodeStyle getStyleOfQualifier(); static NodeStyle getStyleOfTextNode(); - static EdgeStyle getStyleForEdgeType(Edge::EdgeType type, bool isActive, bool isFocused); + static EdgeStyle getStyleForEdgeType(Edge::EdgeType type, bool isActive, bool isFocused, bool isTrailEdge); static int toGridOffset(int x); static int toGridSize(int x); diff --git a/src/lib/data/PersistentStorage.cpp b/src/lib/data/PersistentStorage.cpp index c547bd0e..bb419661 100644 --- a/src/lib/data/PersistentStorage.cpp +++ b/src/lib/data/PersistentStorage.cpp @@ -157,7 +157,8 @@ void PersistentStorage::addError( Id PersistentStorage::addNodeBookmark(const NodeBookmark& bookmark) { const Id categoryId = addBookmarkCategory(bookmark.getCategory().getName()); - const Id id = m_sqliteBookmarkStorage.addBookmark(bookmark.getName(), bookmark.getComment(), bookmark.getTimeStamp().toString(), categoryId); + const Id id = m_sqliteBookmarkStorage.addBookmark( + bookmark.getName(), bookmark.getComment(), bookmark.getTimeStamp().toString(), categoryId); for (const Id& nodeId: bookmark.getNodeIds()) { @@ -170,7 +171,8 @@ Id PersistentStorage::addNodeBookmark(const NodeBookmark& bookmark) Id PersistentStorage::addEdgeBookmark(const EdgeBookmark& bookmark) { const Id categoryId = addBookmarkCategory(bookmark.getCategory().getName()); - const Id id = m_sqliteBookmarkStorage.addBookmark(bookmark.getName(), bookmark.getComment(), bookmark.getTimeStamp().toString(), categoryId); + const Id id = m_sqliteBookmarkStorage.addBookmark( + bookmark.getName(), bookmark.getComment(), bookmark.getTimeStamp().toString(), categoryId); for (const Id& edgeId: bookmark.getEdgeIds()) { const StorageEdge storageEdge = m_sqliteIndexStorage.getEdgeById(edgeId); @@ -178,7 +180,8 @@ Id PersistentStorage::addEdgeBookmark(const EdgeBookmark& bookmark) bool sourceNodeActive = storageEdge.sourceNodeId == bookmark.getActiveNodeId(); m_sqliteBookmarkStorage.addBookmarkedEdge( id, - m_sqliteIndexStorage.getNodeById(storageEdge.sourceNodeId).serializedName, // todo: optimization for multiple edges in same bookmark: use a local cache here + // todo: optimization for multiple edges in same bookmark: use a local cache here + m_sqliteIndexStorage.getNodeById(storageEdge.sourceNodeId).serializedName, m_sqliteIndexStorage.getNodeById(storageEdge.targetNodeId).serializedName, storageEdge.type, sourceNodeActive @@ -202,7 +205,8 @@ Id PersistentStorage::addBookmarkCategory(const std::string& name) return id; } -void PersistentStorage::updateBookmark(const Id bookmarkId, const std::string& name, const std::string& comment, const std::string& categoryName) +void PersistentStorage::updateBookmark( + const Id bookmarkId, const std::string& name, const std::string& comment, const std::string& categoryName) { const Id categoryId = addBookmarkCategory(categoryName); // only creates category if id didn't exist before; m_sqliteBookmarkStorage.updateBookmark(bookmarkId, name, comment, categoryId); @@ -229,7 +233,8 @@ std::vector PersistentStorage::getAllNodeBookmarks() const std::unordered_map> bookmarkIdToBookmarkedNodeIds; for (const StorageBookmarkedNode& bookmarkedNode: m_sqliteBookmarkStorage.getAllBookmarkedNodes()) { - bookmarkIdToBookmarkedNodeIds[bookmarkedNode.bookmarkId].push_back(m_sqliteIndexStorage.getNodeBySerializedName(bookmarkedNode.serializedNodeName).id); + bookmarkIdToBookmarkedNodeIds[bookmarkedNode.bookmarkId].push_back( + m_sqliteIndexStorage.getNodeBySerializedName(bookmarkedNode.serializedNodeName).id); } std::vector nodeBookmarks; @@ -272,7 +277,12 @@ std::vector PersistentStorage::getAllEdgeBookmarks() const std::vector edgeBookmarks; - Cache nodeIdCache([&](std::string serializedNodeName){ return m_sqliteIndexStorage.getNodeBySerializedName(serializedNodeName).id; }); + Cache nodeIdCache([&](std::string serializedNodeName) + { + return m_sqliteIndexStorage.getNodeBySerializedName(serializedNodeName).id; + } + ); + for (const StorageBookmark& storageBookmark: m_sqliteBookmarkStorage.getAllBookmarks()) { auto itCategories = bookmarkCategories.find(storageBookmark.categoryId); @@ -292,7 +302,8 @@ std::vector PersistentStorage::getAllEdgeBookmarks() const { const Id sourceNodeId = nodeIdCache.getValue(bookmarkedEdge.serializedSourceNodeName); const Id targetNodeId = nodeIdCache.getValue(bookmarkedEdge.serializedTargetNodeName); - const Id edgeId = m_sqliteIndexStorage.getEdgeBySourceTargetType(sourceNodeId, targetNodeId, bookmarkedEdge.edgeType).id; + const Id edgeId = + m_sqliteIndexStorage.getEdgeBySourceTargetType(sourceNodeId, targetNodeId, bookmarkedEdge.edgeType).id; bookmark.addEdgeId(edgeId); if (activeNodeId == 0) @@ -360,7 +371,8 @@ void PersistentStorage::forEachLocalSymbol(std::function callback) const +void PersistentStorage::forEachSourceLocation( + std::function callback) const { for (StorageSourceLocation& sourceLocation: m_sqliteIndexStorage.getAll()) { @@ -767,7 +779,8 @@ std::vector PersistentStorage::getAutocompletionMatches(const std:: return matches; } -std::vector PersistentStorage::getAutocompletionSymbolMatches(const std::string& query, size_t maxResultsCount) const +std::vector PersistentStorage::getAutocompletionSymbolMatches( + const std::string& query, size_t maxResultsCount) const { // search in indices std::vector results = m_symbolIndex.search(query, maxResultsCount, maxResultsCount); @@ -985,7 +998,8 @@ std::shared_ptr PersistentStorage::getGraphForAll() const return graph; } -std::shared_ptr PersistentStorage::getGraphForActiveTokenIds(const std::vector& tokenIds, bool* isActiveNamespace) const +std::shared_ptr PersistentStorage::getGraphForActiveTokenIds( + const std::vector& tokenIds, bool* isActiveNamespace) const { TRACE(); @@ -1108,6 +1122,71 @@ std::shared_ptr PersistentStorage::getGraphForActiveTokenIds(const std::v return g; } +std::shared_ptr PersistentStorage::getGraphForTrail( + Id originId, Id targetId, Edge::EdgeTypeMask trailType, size_t depth) const +{ + TRACE(); + + std::set nodeIds; + std::set edgeIds; + + std::vector nodeIdsToProcess; + nodeIdsToProcess.push_back(originId ? originId : targetId); + bool forward = originId; + size_t currentDepth = 0; + + nodeIds.insert(nodeIdsToProcess.back()); + while (nodeIdsToProcess.size() && (!depth || currentDepth < depth)) + { + std::vector edges = + forward ? + m_sqliteIndexStorage.getEdgesBySourceIds(nodeIdsToProcess) : + m_sqliteIndexStorage.getEdgesByTargetIds(nodeIdsToProcess); + + if (trailType & (Edge::EDGE_OVERRIDE | Edge::EDGE_INHERITANCE)) + { + utility::append(edges, + forward ? + m_sqliteIndexStorage.getEdgesByTargetIds(nodeIdsToProcess) : + m_sqliteIndexStorage.getEdgesBySourceIds(nodeIdsToProcess) + ); + } + + nodeIdsToProcess.clear(); + + for (const StorageEdge& edge : edges) + { + if (Edge::intToType(edge.type) & trailType) + { + bool isForward = forward == !(Edge::intToType(edge.type) & (Edge::EDGE_OVERRIDE | Edge::EDGE_INHERITANCE)); + + Id nodeId = isForward ? edge.targetNodeId : edge.sourceNodeId; + Id otherNodeId = isForward ? edge.sourceNodeId : edge.targetNodeId; + + if (nodeIds.find(nodeId) == nodeIds.end()) + { + nodeIdsToProcess.push_back(nodeId); + nodeIds.insert(nodeId); + edgeIds.insert(edge.id); + } + else if (nodeIds.find(otherNodeId) != nodeIds.end()) + { + edgeIds.insert(edge.id); + } + } + } + + currentDepth++; + } + + std::shared_ptr graph = std::make_shared(); + + addNodesWithChildrenAndEdgesToGraph(utility::toVector(nodeIds), utility::toVector(edgeIds), graph.get()); + addComponentAccessToGraph(graph.get()); + + return graph; +} + // TODO: rename: getActiveElementIdsForId; TODO: make separate function for declarationId std::vector PersistentStorage::getActiveTokenIdsForId(Id tokenId, Id* declarationId) const { @@ -1506,7 +1585,8 @@ std::unordered_map> PersistentStorage::getFileIdToImportingFile importedSourceLocationToElementIds[occurrence.sourceLocationId] = occurrence.elementId; } - for (const StorageSourceLocation& sourceLocation: m_sqliteIndexStorage.getAllByIds(importedSourceLocationIds)) + for (const StorageSourceLocation& sourceLocation: + m_sqliteIndexStorage.getAllByIds(importedSourceLocationIds)) { auto it = importedSourceLocationToElementIds.find(sourceLocation.id); if (it != importedSourceLocationToElementIds.end()) @@ -1528,7 +1608,8 @@ std::unordered_map> PersistentStorage::getFileIdToImportingFile return fileIdToImportingFileIdMap; } -std::set PersistentStorage::getReferenced(const std::set& ids, std::unordered_map> idToReferencingIdMap) const +std::set PersistentStorage::getReferenced( + const std::set& ids, std::unordered_map> idToReferencingIdMap) const { std::unordered_map> idToReferencedIdMap; for (auto it: idToReferencingIdMap) @@ -1542,7 +1623,8 @@ std::set PersistentStorage::getReferenced(const std::set& ids, std::unor return getReferencing(ids, idToReferencedIdMap); } -std::set PersistentStorage::getReferencing(const std::set& ids, std::unordered_map> idToReferencingIdMap) const +std::set PersistentStorage::getReferencing( + const std::set& ids, std::unordered_map> idToReferencingIdMap) const { std::set referencingIds; diff --git a/src/lib/data/PersistentStorage.h b/src/lib/data/PersistentStorage.h index 0410c013..0eecb1d5 100644 --- a/src/lib/data/PersistentStorage.h +++ b/src/lib/data/PersistentStorage.h @@ -117,6 +117,7 @@ public: virtual std::shared_ptr getGraphForAll() const; virtual std::shared_ptr getGraphForActiveTokenIds(const std::vector& tokenIds, bool* isActiveNamespace = nullptr) const; + virtual std::shared_ptr getGraphForTrail(Id originId, Id targetId, Edge::EdgeTypeMask trailType, size_t depth) const; virtual std::vector getActiveTokenIdsForId(Id tokenId, Id* declarationId) const; virtual std::vector getNodeIdsForLocationIds(const std::vector& locationIds) const; diff --git a/src/lib/data/access/StorageAccess.h b/src/lib/data/access/StorageAccess.h index e20dd5ec..2fd8dc53 100644 --- a/src/lib/data/access/StorageAccess.h +++ b/src/lib/data/access/StorageAccess.h @@ -52,6 +52,7 @@ public: virtual std::shared_ptr getGraphForAll() const = 0; virtual std::shared_ptr getGraphForActiveTokenIds(const std::vector& tokenIds, bool* isActiveNamespace = nullptr) const = 0; + virtual std::shared_ptr getGraphForTrail(Id originId, Id targetId, Edge::EdgeTypeMask trailType, size_t depth) const = 0; virtual std::vector getActiveTokenIdsForId(Id tokenId, Id* declarationId) const = 0; virtual std::vector getNodeIdsForLocationIds(const std::vector& locationIds) const = 0; diff --git a/src/lib/data/access/StorageAccessProxy.cpp b/src/lib/data/access/StorageAccessProxy.cpp index 5f41b0b8..6f1e98d6 100644 --- a/src/lib/data/access/StorageAccessProxy.cpp +++ b/src/lib/data/access/StorageAccessProxy.cpp @@ -184,6 +184,16 @@ std::shared_ptr StorageAccessProxy::getGraphForActiveTokenIds(const std:: return std::make_shared(); } +std::shared_ptr StorageAccessProxy::getGraphForTrail(Id originId, Id targetId, Edge::EdgeTypeMask trailType, size_t depth) const +{ + if (hasSubject()) + { + return m_subject->getGraphForTrail(originId, targetId, trailType, depth); + } + + return std::make_shared(); +} + std::vector StorageAccessProxy::getActiveTokenIdsForId(Id tokenId, Id* delcarationId) const { if (hasSubject()) diff --git a/src/lib/data/access/StorageAccessProxy.h b/src/lib/data/access/StorageAccessProxy.h index 45169246..de05bf7c 100644 --- a/src/lib/data/access/StorageAccessProxy.h +++ b/src/lib/data/access/StorageAccessProxy.h @@ -40,6 +40,7 @@ public: virtual std::shared_ptr getGraphForAll() const; virtual std::shared_ptr getGraphForActiveTokenIds(const std::vector& tokenIds, bool* isActiveNamespace = nullptr) const; + virtual std::shared_ptr getGraphForTrail(Id originId, Id targetId, Edge::EdgeTypeMask trailType, size_t depth) const; virtual std::vector getActiveTokenIdsForId(Id tokenId, Id* declarationId) const; virtual std::vector getNodeIdsForLocationIds(const std::vector& locationIds) const; diff --git a/src/lib/data/graph/Graph.cpp b/src/lib/data/graph/Graph.cpp index ec2bba28..11d7439c 100644 --- a/src/lib/data/graph/Graph.cpp +++ b/src/lib/data/graph/Graph.cpp @@ -3,6 +3,7 @@ #include "utility/logging/logging.h" Graph::Graph() + : m_trailMode(TRAIL_NONE) { } @@ -285,6 +286,16 @@ Token* Graph::getTokenById(Id id) const return token; } +Graph::TrailMode Graph::getTrailMode() const +{ + return m_trailMode; +} + +void Graph::setTrailMode(TrailMode trailMode) +{ + m_trailMode = trailMode; +} + void Graph::print(std::ostream& ostream) const { ostream << "Graph:\n"; diff --git a/src/lib/data/graph/Graph.h b/src/lib/data/graph/Graph.h index 82c6f7b1..4d46b0d5 100644 --- a/src/lib/data/graph/Graph.h +++ b/src/lib/data/graph/Graph.h @@ -11,6 +11,13 @@ class Graph { public: + enum TrailMode + { + TRAIL_NONE, + TRAIL_HORIZONTAL, + TRAIL_VERTICAL + }; + Graph(); virtual ~Graph(); @@ -49,6 +56,9 @@ public: Token* getTokenById(Id id) const; + TrailMode getTrailMode() const; + void setTrailMode(TrailMode trailMode); + void print(std::ostream& ostream) const; void printBasic(std::ostream& ostream) const; @@ -60,6 +70,8 @@ private: std::map> m_nodes; std::map> m_edges; + + TrailMode m_trailMode; }; std::ostream& operator<<(std::ostream& ostream, const Graph& graph); diff --git a/src/lib/utility/messaging/type/MessageActivateTrail.h b/src/lib/utility/messaging/type/MessageActivateTrail.h new file mode 100644 index 00000000..4ea1189c --- /dev/null +++ b/src/lib/utility/messaging/type/MessageActivateTrail.h @@ -0,0 +1,32 @@ +#ifndef MESSAGE_ACTIVATE_TRAIL_H +#define MESSAGE_ACTIVATE_TRAIL_H + +#include "utility/messaging/Message.h" +#include "utility/types.h" + +class MessageActivateTrail + : public Message +{ +public: + MessageActivateTrail(Id originId, Id targetId, Edge::EdgeTypeMask trailType, size_t depth, bool horizontalLayout) + : originId(originId) + , targetId(targetId) + , trailType(trailType) + , depth(depth) + , horizontalLayout(horizontalLayout) + { + } + + static const std::string getStaticType() + { + return "MessageActivateTrail"; + } + + const Id originId; + const Id targetId; + const Edge::EdgeTypeMask trailType; + const size_t depth; + const bool horizontalLayout; +}; + +#endif // MESSAGE_ACTIVATE_TRAIL_H diff --git a/src/lib_gui/CMakeLists.txt b/src/lib_gui/CMakeLists.txt index 477152f0..6b6e74e9 100644 --- a/src/lib_gui/CMakeLists.txt +++ b/src/lib_gui/CMakeLists.txt @@ -57,16 +57,20 @@ add_files( qt/element/QtUndoRedo.cpp qt/element/QtUndoRedo.h - qt/graphics/QtAngledLineItem.cpp - qt/graphics/QtAngledLineItem.h qt/graphics/QtCountCircleItem.cpp qt/graphics/QtCountCircleItem.h qt/graphics/QtGraphicsView.cpp qt/graphics/QtGraphicsView.h + qt/graphics/QtLineItemAngled.cpp + qt/graphics/QtLineItemAngled.h + qt/graphics/QtLineItemBase.cpp + qt/graphics/QtLineItemBase.h + qt/graphics/QtLineItemBezier.cpp + qt/graphics/QtLineItemBezier.h + qt/graphics/QtLineItemStraight.cpp + qt/graphics/QtLineItemStraight.h qt/graphics/QtRoundedRectItem.cpp qt/graphics/QtRoundedRectItem.h - qt/graphics/QtStraightLineItem.cpp - qt/graphics/QtStraightLineItem.h qt/network/QtIDECommunicationController.cpp qt/network/QtIDECommunicationController.h diff --git a/src/lib_gui/qt/graphics/QtLineItemAngled.cpp b/src/lib_gui/qt/graphics/QtLineItemAngled.cpp new file mode 100644 index 00000000..fadb941a --- /dev/null +++ b/src/lib_gui/qt/graphics/QtLineItemAngled.cpp @@ -0,0 +1,181 @@ +#include "qt/graphics/QtLineItemAngled.h" + +#include + +#include +#include + +QtLineItemAngled::QtLineItemAngled(QGraphicsItem* parent) + : QtLineItemBase(parent) +{ + this->setFlag(QGraphicsItem::ItemUsesExtendedStyleOption, true); +} + +QtLineItemAngled::~QtLineItemAngled() +{ +} + +QPainterPath QtLineItemAngled::shape() const +{ + QPainterPath path; + QPolygon poly = getPath(); + + for (int i = 0; i < poly.size() - 1; i++) + { + path.addRect(QRectF(poly.at(i), poly.at(i + 1)).normalized().adjusted(-5, -5, 5, 5)); + } + + path.addRect(getArrowBoundingRect(poly).adjusted(-3, -3, 3, 3)); + + return path; +} + +void QtLineItemAngled::paint(QPainter* painter, const QStyleOptionGraphicsItem* options, QWidget* widget) +{ + painter->setPen(pen()); + + QPainterPath path; + + QPolygon poly = getPath(); + int i = poly.length() - 1; + + path.moveTo(poly.at(i)); + + + // while (i > 0) + // { + // i--; + + // path.lineTo(poly.at(i)); + // } + // painter->drawPath(path); + // return; + + + QRectF drawRect = options->exposedRect; + QRectF partRect; + + int radius = m_style.cornerRadius; + int dir = getDirection(poly.at(i), poly.at(i - 1)); + while (i > 1) + { + i--; + + QPointF a = poly.at(i); + QPointF b = poly.at(i - 1); + + int newDir = getDirection(a, b); + int ar = radius; + int br = m_style.cornerRadius; + + if (i != 1) + { + if (dir % 2 == 1 && std::abs(a.y() - b.y()) < 2 * br) + { + br = std::abs(a.y() - b.y()) / 2; + } + else if (dir % 2 == 0 && std::abs(a.x() - b.x()) < 2 * br) + { + br = std::abs(a.x() - b.x()) / 2; + } + } + + switch (dir) + { + case 0: a.setY(a.y() + ar); break; + case 1: a.setX(a.x() - ar); break; + case 2: a.setY(a.y() - ar); break; + case 3: a.setX(a.x() + ar); break; + } + + b = poly.at(i); + switch (newDir) + { + case 0: b.setY(b.y() - br); break; + case 1: b.setX(b.x() + br); break; + case 2: b.setY(b.y() + br); break; + case 3: b.setX(b.x() - br); break; + } + + partRect = QRectF(poly.at(i + 1), poly.at(i)).adjusted(-1, -1, 1, 1); + if (drawRect.intersects(partRect)) + { + path.lineTo(a); + } + else + { + path.moveTo(a); + } + + partRect = QRectF(a, b).normalized().adjusted(-1, -1, 1, 1); + if (drawRect.intersects(partRect)) + { + switch (dir) + { + case 0: + if (newDir == 1) + { + path.arcTo(a.x(), b.y(), 2 * br, 2 * ar, 180, -90); + } + else if (newDir == 3) + { + path.arcTo(b.x() - br, a.y() - ar, 2 * br, 2 * ar, 0, 90); + } + break; + case 1: + if (newDir == 0) + { + path.arcTo(a.x() - ar, b.y() - br, 2 * ar, 2 * br, -90, 90); + } + else if (newDir == 2) + { + path.arcTo(a.x() - ar, a.y(), 2 * ar, 2 * br, 90, -90); + } + break; + case 2: + if (newDir == 1) + { + path.arcTo(a.x(), a.y() - ar, 2 * br, 2 * ar, 180, 90); + } + else if (newDir == 3) + { + path.arcTo(b.x() - br, a.y() - ar, 2 * br, 2 * ar, 0, -90); + } + break; + case 3: + if (newDir == 0) + { + path.arcTo(b.x(), b.y() - br, 2 * ar, 2 * br, -90, -90); + } + else if (newDir == 2) + { + path.arcTo(b.x(), a.y(), 2 * ar, 2 * br, 90, 90); + } + break; + } + } + else + { + path.moveTo(b); + } + + dir = newDir; + radius = br; + } + + partRect = QRectF(poly.at(0), poly.at(1)).adjusted(-1, -1, 1, 1); + if (drawRect.intersects(partRect)) + { + partRect = getArrowBoundingRect(poly); + if (drawRect.intersects(partRect) && m_showArrow) + { + drawArrow(poly, &path); + } + else + { + path.lineTo(poly.at(0)); + } + } + + painter->drawPath(path); +} diff --git a/src/lib_gui/qt/graphics/QtLineItemAngled.h b/src/lib_gui/qt/graphics/QtLineItemAngled.h new file mode 100644 index 00000000..55e59317 --- /dev/null +++ b/src/lib_gui/qt/graphics/QtLineItemAngled.h @@ -0,0 +1,17 @@ +#ifndef QT_LINE_ITEM_ANGLED_H +#define QT_LINE_ITEM_ANGLED_H + +#include "qt/graphics/QtLineItemBase.h" + +class QtLineItemAngled + : public QtLineItemBase +{ +public: + QtLineItemAngled(QGraphicsItem* parent); + virtual ~QtLineItemAngled(); + + virtual QPainterPath shape() const; + virtual void paint(QPainter* painter, const QStyleOptionGraphicsItem* options, QWidget* widget); +}; + +#endif // QT_LINE_ITEM_ANGLED_H diff --git a/src/lib_gui/qt/graphics/QtAngledLineItem.cpp b/src/lib_gui/qt/graphics/QtLineItemBase.cpp similarity index 61% rename from src/lib_gui/qt/graphics/QtAngledLineItem.cpp rename to src/lib_gui/qt/graphics/QtLineItemBase.cpp index 969ac2b6..b6bfed35 100644 --- a/src/lib_gui/qt/graphics/QtAngledLineItem.cpp +++ b/src/lib_gui/qt/graphics/QtLineItemBase.cpp @@ -1,30 +1,25 @@ -#include "qt/graphics/QtAngledLineItem.h" +#include "qt/graphics/QtLineItemBase.h" -#include +#include +#include -#include -#include - -#include "utility/math/Vector2.h" - -QtAngledLineItem::QtAngledLineItem(QGraphicsItem* parent) +QtLineItemBase::QtLineItemBase(QGraphicsItem* parent) : QGraphicsLineItem(parent) - , m_route(ROUTE_ANY) - , m_pivot(PIVOT_THIRD) + , m_showArrow(true) , m_onFront(false) , m_onBack(false) , m_horizontalIn(false) - , m_showArrow(true) + , m_route(ROUTE_ANY) + , m_pivot(PIVOT_THIRD) { this->setAcceptHoverEvents(true); - this->setFlag(QGraphicsItem::ItemUsesExtendedStyleOption, true); } -QtAngledLineItem::~QtAngledLineItem() +QtLineItemBase::~QtLineItemBase() { } -void QtAngledLineItem::updateLine( +void QtLineItemBase::updateLine( Vec4i ownerRect, Vec4i targetRect, Vec4i ownerParentRect, Vec4i targetParentRect, GraphViewStyle::EdgeStyle style, @@ -49,197 +44,32 @@ void QtAngledLineItem::updateLine( this->setPen(QPen(QBrush(style.color.c_str()), style.width + int(log10(weight)), Qt::SolidLine, Qt::RoundCap)); } -void QtAngledLineItem::setRoute(Route route) +void QtLineItemBase::setRoute(Route route) { m_route = route; } -void QtAngledLineItem::setPivot(Pivot pivot) +void QtLineItemBase::setPivot(Pivot pivot) { m_pivot = pivot; } -void QtAngledLineItem::setOnFront(bool front) +void QtLineItemBase::setOnFront(bool front) { m_onFront = front; } -void QtAngledLineItem::setOnBack(bool back) +void QtLineItemBase::setOnBack(bool back) { m_onBack = back; } -void QtAngledLineItem::setHorizontalIn(bool horizontal) +void QtLineItemBase::setHorizontalIn(bool horizontal) { m_horizontalIn = horizontal; } -QPainterPath QtAngledLineItem::shape() const -{ - QPainterPath path; - QPolygon poly = getPath(); - - for (int i = 0; i < poly.size() - 1; i++) - { - path.addRect(QRectF(poly.at(i), poly.at(i + 1)).normalized().adjusted(-3, -3, 3, 3)); - } - - path.addRect(getArrowBoundingRect(poly).adjusted(-3, -3, 3, 3)); - - return path; -} - -void QtAngledLineItem::paint(QPainter* painter, const QStyleOptionGraphicsItem* options, QWidget* widget) -{ - painter->setPen(pen()); - - QPainterPath path; - - QPolygon poly = getPath(); - int i = poly.length() - 1; - - path.moveTo(poly.at(i)); - - - // while (i > 0) - // { - // i--; - - // path.lineTo(poly.at(i)); - // } - // painter->drawPath(path); - // return; - - - QRectF drawRect = options->exposedRect; - QRectF partRect; - - int radius = m_style.cornerRadius; - int dir = getDirection(poly.at(i), poly.at(i - 1)); - while (i > 1) - { - i--; - - QPointF a = poly.at(i); - QPointF b = poly.at(i - 1); - - int newDir = getDirection(a, b); - int ar = radius; - int br = m_style.cornerRadius; - - if (i != 1) - { - if (dir % 2 == 1 && std::abs(a.y() - b.y()) < 2 * br) - { - br = std::abs(a.y() - b.y()) / 2; - } - else if (dir % 2 == 0 && std::abs(a.x() - b.x()) < 2 * br) - { - br = std::abs(a.x() - b.x()) / 2; - } - } - - switch (dir) - { - case 0: a.setY(a.y() + ar); break; - case 1: a.setX(a.x() - ar); break; - case 2: a.setY(a.y() - ar); break; - case 3: a.setX(a.x() + ar); break; - } - - b = poly.at(i); - switch (newDir) - { - case 0: b.setY(b.y() - br); break; - case 1: b.setX(b.x() + br); break; - case 2: b.setY(b.y() + br); break; - case 3: b.setX(b.x() - br); break; - } - - partRect = QRectF(poly.at(i + 1), poly.at(i)).adjusted(-1, -1, 1, 1); - if (drawRect.intersects(partRect)) - { - path.lineTo(a); - } - else - { - path.moveTo(a); - } - - partRect = QRectF(a, b).normalized().adjusted(-1, -1, 1, 1); - if (drawRect.intersects(partRect)) - { - switch (dir) - { - case 0: - if (newDir == 1) - { - path.arcTo(a.x(), b.y(), 2 * br, 2 * ar, 180, -90); - } - else if (newDir == 3) - { - path.arcTo(b.x() - br, a.y() - ar, 2 * br, 2 * ar, 0, 90); - } - break; - case 1: - if (newDir == 0) - { - path.arcTo(a.x() - ar, b.y() - br, 2 * ar, 2 * br, -90, 90); - } - else if (newDir == 2) - { - path.arcTo(a.x() - ar, a.y(), 2 * ar, 2 * br, 90, -90); - } - break; - case 2: - if (newDir == 1) - { - path.arcTo(a.x(), a.y() - ar, 2 * br, 2 * ar, 180, 90); - } - else if (newDir == 3) - { - path.arcTo(b.x() - br, a.y() - ar, 2 * br, 2 * ar, 0, -90); - } - break; - case 3: - if (newDir == 0) - { - path.arcTo(b.x(), b.y() - br, 2 * ar, 2 * br, -90, -90); - } - else if (newDir == 2) - { - path.arcTo(b.x(), a.y(), 2 * ar, 2 * br, 90, 90); - } - break; - } - } - else - { - path.moveTo(b); - } - - dir = newDir; - radius = br; - } - - partRect = QRectF(poly.at(0), poly.at(1)).adjusted(-1, -1, 1, 1); - if (drawRect.intersects(partRect)) - { - partRect = getArrowBoundingRect(poly); - if (drawRect.intersects(partRect) && m_showArrow) - { - drawArrow(poly, &path); - } - else - { - path.lineTo(poly.at(0)); - } - } - - painter->drawPath(path); -} - -QPolygon QtAngledLineItem::getPath() const +QPolygon QtLineItemBase::getPath() const { if (m_polygon.size() > 0) { @@ -448,7 +278,7 @@ QPolygon QtAngledLineItem::getPath() const return poly; } -int QtAngledLineItem::getDirection(const QPointF& a, const QPointF& b) const +int QtLineItemBase::getDirection(const QPointF& a, const QPointF& b) const { if (a.x() != b.x()) { @@ -474,7 +304,7 @@ int QtAngledLineItem::getDirection(const QPointF& a, const QPointF& b) const } } -QRectF QtAngledLineItem::getArrowBoundingRect(const QPolygon& poly) const +QRectF QtLineItemBase::getArrowBoundingRect(const QPolygon& poly) const { int dir = getDirection(poly.at(1), poly.at(0)); @@ -506,7 +336,7 @@ QRectF QtAngledLineItem::getArrowBoundingRect(const QPolygon& poly) const return rect; } -void QtAngledLineItem::drawArrow(const QPolygon& poly, QPainterPath* path) const +void QtLineItemBase::drawArrow(const QPolygon& poly, QPainterPath* path) const { int dir = getDirection(poly.at(1), poly.at(0)); @@ -559,7 +389,7 @@ void QtAngledLineItem::drawArrow(const QPolygon& poly, QPainterPath* path) const path->lineTo(tip); } -void QtAngledLineItem::getPivotPoints(Vec2f* p, const Vec4i& in, const Vec4i& out, int offset, bool target) const +void QtLineItemBase::getPivotPoints(Vec2f* p, const Vec4i& in, const Vec4i& out, int offset, bool target) const { float f = m_pivot == PIVOT_THIRD ? (target ? 2 / 3.f : 1 / 3.f) : 1 / 2.f; diff --git a/src/lib_gui/qt/graphics/QtAngledLineItem.h b/src/lib_gui/qt/graphics/QtLineItemBase.h similarity index 78% rename from src/lib_gui/qt/graphics/QtAngledLineItem.h rename to src/lib_gui/qt/graphics/QtLineItemBase.h index 855e9b8b..d1105295 100644 --- a/src/lib_gui/qt/graphics/QtAngledLineItem.h +++ b/src/lib_gui/qt/graphics/QtLineItemBase.h @@ -1,5 +1,5 @@ -#ifndef QT_ANGLED_LINE_ITEM_H -#define QT_ANGLED_LINE_ITEM_H +#ifndef QT_LINE_ITEM_BASE_H +#define QT_LINE_ITEM_BASE_H #include @@ -7,7 +7,7 @@ #include "component/view/GraphViewStyle.h" -class QtAngledLineItem +class QtLineItemBase : public QGraphicsLineItem { public: @@ -24,8 +24,8 @@ public: PIVOT_MIDDLE }; - QtAngledLineItem(QGraphicsItem* parent); - virtual ~QtAngledLineItem(); + QtLineItemBase(QGraphicsItem* parent); + virtual ~QtLineItemBase(); void updateLine( Vec4i ownerRect, Vec4i targetRect, @@ -40,10 +40,7 @@ public: void setOnBack(bool back); void setHorizontalIn(bool horizontal); - virtual QPainterPath shape() const; - virtual void paint(QPainter* painter, const QStyleOptionGraphicsItem* options, QWidget* widget); - -private: +protected: QPolygon getPath() const; int getDirection(const QPointF& a, const QPointF& b) const; @@ -52,23 +49,24 @@ private: void getPivotPoints(Vec2f* p, const Vec4i& in, const Vec4i& out, int offset, bool target) const; + GraphViewStyle::EdgeStyle m_style; + bool m_showArrow; + + bool m_onFront; + bool m_onBack; + bool m_horizontalIn; + + Route m_route; + Pivot m_pivot; + +private: Vec4i m_ownerRect; Vec4i m_targetRect; Vec4i m_ownerParentRect; Vec4i m_targetParentRect; - GraphViewStyle::EdgeStyle m_style; - - Route m_route; - Pivot m_pivot; - - bool m_onFront; - bool m_onBack; - bool m_horizontalIn; - bool m_showArrow; - mutable QPolygon m_polygon; }; -#endif // QT_ANGLED_LINE_ITEM_H +#endif // QT_LINE_ITEM_BASE_H diff --git a/src/lib_gui/qt/graphics/QtLineItemBezier.cpp b/src/lib_gui/qt/graphics/QtLineItemBezier.cpp new file mode 100644 index 00000000..d7f6b05f --- /dev/null +++ b/src/lib_gui/qt/graphics/QtLineItemBezier.cpp @@ -0,0 +1,143 @@ +#include "qt/graphics/QtLineItemBezier.h" + +#include + +QtLineItemBezier::QtLineItemBezier(QGraphicsItem* parent) + : QtLineItemBase(parent) +{ +} + +QtLineItemBezier::~QtLineItemBezier() +{ +} + +QPainterPath QtLineItemBezier::shape() const +{ + QPainterPathStroker stroker; + stroker.setWidth(10); + stroker.setJoinStyle(Qt::MiterJoin); + + QPainterPath path = stroker.createStroke(getCurve()); + if (m_showArrow) + { + path.addRect(getArrowBoundingRect(QtLineItemBase::getPath()).adjusted(-3, -3, 3, 3)); + } + return path; +} + +void QtLineItemBezier::paint(QPainter* painter, const QStyleOptionGraphicsItem* options, QWidget* widget) +{ + QPainterPath path = getCurve(); + + if (m_showArrow) + { + drawArrow(QtLineItemBase::getPath(), &path); + } + + painter->setPen(pen()); + painter->drawPath(path); +} + +QPolygon QtLineItemBezier::getPath() const +{ + QPolygon poly = QtLineItemBase::getPath(); + QPolygon newPoly; + + QPoint tip = poly.at(0); + + if (m_showArrow && m_style.arrowClosed) + { + int dir = getDirection(poly.at(1), poly.at(0)); + + switch (dir) + { + case 0: + tip.ry() += m_style.arrowLength; + break; + case 1: + tip.rx() -= m_style.arrowLength; + break; + case 2: + tip.ry() -= m_style.arrowLength; + break; + case 3: + tip.rx() += m_style.arrowLength; + break; + } + } + + newPoly << tip; + + QRect rect(poly.at(0), poly.at(3)); + + if (!rect.contains(poly.at(1))) + { + if (m_route == ROUTE_HORIZONTAL) + { + int diff; + if (std::abs(poly.at(1).x() - poly.at(0).x()) < std::abs(poly.at(2).x() - poly.at(3).x())) + { + diff = poly.at(1).x() - poly.at(0).x(); + } + else + { + diff = poly.at(2).x() - poly.at(3).x(); + } + + newPoly << QPoint(poly.at(1).x() + diff, poly.at(1).y()); + newPoly << QPoint(poly.at(2).x() + diff, poly.at(2).y()); + } + else + { + int diff; + if (std::abs(poly.at(1).y() - poly.at(0).y()) < std::abs(poly.at(2).y() - poly.at(3).y())) + { + diff = poly.at(1).y() - poly.at(0).y(); + } + else + { + diff = poly.at(2).y() - poly.at(3).y(); + } + + newPoly << QPoint(poly.at(1).x(), poly.at(1).y() + diff); + newPoly << QPoint(poly.at(2).x(), poly.at(2).y() + diff); + } + } + else + { + if (m_route == ROUTE_HORIZONTAL) + { + newPoly << QPoint((poly.at(3).x() + poly.at(0).x()) / 2, poly.at(0).y()); + newPoly << QPoint((poly.at(3).x() + poly.at(0).x()) / 2, poly.at(3).y()); + } + else + { + newPoly << QPoint(poly.at(0).x(), (poly.at(3).y() + poly.at(0).y()) / 2); + newPoly << QPoint(poly.at(3).x(), (poly.at(3).y() + poly.at(0).y()) / 2); + } + } + + newPoly << poly.at(3); + return newPoly; +} + +QPainterPath QtLineItemBezier::getCurve() const +{ + QPolygon poly = getPath(); + + QPainterPath path; + path.moveTo(poly.at(3)); + path.cubicTo( + poly.at(2), + poly.at(1), + poly.at(0) + ); + + // QPainterPath path; + // path.moveTo(poly.at(3)); + // path.lineTo(poly.at(2)); + // path.lineTo(poly.at(1)); + // path.lineTo(poly.at(0)); + + return path; +} diff --git a/src/lib_gui/qt/graphics/QtLineItemBezier.h b/src/lib_gui/qt/graphics/QtLineItemBezier.h new file mode 100644 index 00000000..6962878b --- /dev/null +++ b/src/lib_gui/qt/graphics/QtLineItemBezier.h @@ -0,0 +1,23 @@ +#ifndef QT_LINE_ITEM_BEZIER_H +#define QT_LINE_ITEM_BEZIER_H + +#include "qt/graphics/QtLineItemBase.h" + +class QtLineItemBezier + : public QtLineItemBase +{ +public: + QtLineItemBezier(QGraphicsItem* parent); + virtual ~QtLineItemBezier(); + + virtual QPainterPath shape() const; + virtual void paint(QPainter* painter, const QStyleOptionGraphicsItem* options, QWidget* widget); + +protected: + QPolygon getPath() const; + +private: + QPainterPath getCurve() const; +}; + +#endif // QT_LINE_ITEM_BEZIER_H diff --git a/src/lib_gui/qt/graphics/QtLineItemStraight.cpp b/src/lib_gui/qt/graphics/QtLineItemStraight.cpp new file mode 100644 index 00000000..3a03dc91 --- /dev/null +++ b/src/lib_gui/qt/graphics/QtLineItemStraight.cpp @@ -0,0 +1,38 @@ +#include "qt/graphics/QtLineItemStraight.h" + +#include +#include + +QtLineItemStraight::QtLineItemStraight(QGraphicsItem* parent) + : QGraphicsLineItem(parent) +{ + this->setAcceptHoverEvents(true); +} + +QtLineItemStraight::~QtLineItemStraight() +{ +} + +void QtLineItemStraight::updateLine(Vec2i origin, Vec2i target, GraphViewStyle::EdgeStyle style) +{ + prepareGeometryChange(); + + setLine(origin.x(), origin.y(), target.x(), target.y()); + + setPen(QPen(QBrush(style.color.c_str()), style.width, Qt::SolidLine)); +} + +QPainterPath QtLineItemStraight::shape() const +{ + QPainterPath path; + QLineF l = line(); + + path.moveTo(l.p1()); + path.lineTo(l.p2()); + + QPainterPathStroker stroker; + stroker.setWidth(10); + stroker.setJoinStyle(Qt::MiterJoin); + + return stroker.createStroke(path); +} diff --git a/src/lib_gui/qt/graphics/QtLineItemStraight.h b/src/lib_gui/qt/graphics/QtLineItemStraight.h new file mode 100644 index 00000000..bee6a6f5 --- /dev/null +++ b/src/lib_gui/qt/graphics/QtLineItemStraight.h @@ -0,0 +1,20 @@ +#ifndef QT_LINE_ITEM_STRAIGHT_H +#define QT_LINE_ITEM_STRAIGHT_H + +#include + +#include "component/view/GraphViewStyle.h" + +class QtLineItemStraight + : public QGraphicsLineItem +{ +public: + QtLineItemStraight(QGraphicsItem* parent); + virtual ~QtLineItemStraight(); + + void updateLine(Vec2i origin, Vec2i target, GraphViewStyle::EdgeStyle style); + + virtual QPainterPath shape() const; +}; + +#endif // QT_LINE_ITEM_STRAIGHT_H diff --git a/src/lib_gui/qt/graphics/QtStraightLineItem.cpp b/src/lib_gui/qt/graphics/QtStraightLineItem.cpp deleted file mode 100644 index 108cd005..00000000 --- a/src/lib_gui/qt/graphics/QtStraightLineItem.cpp +++ /dev/null @@ -1,110 +0,0 @@ -#include "qt/graphics/QtStraightLineItem.h" - -#include -#include - -#include "utility/math/Vector2.h" -#include "utility/utility.h" - -#include "qt/graphics/QtCountCircleItem.h" - -QtStraightLineItem::QtStraightLineItem(QGraphicsItem* parent) - : QGraphicsLineItem(parent) -{ - this->setAcceptHoverEvents(true); - - m_circle = new QtCountCircleItem(this); - - m_arrowLeft = new QGraphicsLineItem(this); - m_arrowRight = new QGraphicsLineItem(this); -} - -QtStraightLineItem::~QtStraightLineItem() -{ -} - -void QtStraightLineItem::updateLine( - Vec4i ownerRect, Vec4i targetRect, int number, - GraphViewStyle::EdgeStyle style, GraphViewStyle::NodeStyle countStyle, bool showArrow -){ - prepareGeometryChange(); - - const Vec4i& o = ownerRect; - const Vec4i& t = targetRect; - - Vec2f oc((o.x + o.z) / 2, (o.y + o.w) / 2); - Vec2f tc((t.x + t.z) / 2, (t.y + t.w) / 2); - - Vec2f op; - Vec2f tp; - - bool intersects = false; - for (int i = 0; !intersects && i < 4; i++) - { - switch (i) - { - case 0: intersects = utility::intersectionPoint(oc, tc, Vec2f(o.x, o.y), Vec2f(o.x, o.w), &op); break; - case 1: intersects = utility::intersectionPoint(oc, tc, Vec2f(o.x, o.y), Vec2f(o.z, o.y), &op); break; - case 2: intersects = utility::intersectionPoint(oc, tc, Vec2f(o.z, o.y), Vec2f(o.z, o.w), &op); break; - case 3: intersects = utility::intersectionPoint(oc, tc, Vec2f(o.x, o.w), Vec2f(o.z, o.w), &op); break; - } - } - - if (!intersects) - { - op = oc; - } - - intersects = false; - for (int i = 0; !intersects && i < 4; i++) - { - switch (i) - { - case 0: intersects = utility::intersectionPoint(oc, tc, Vec2f(t.x, t.y), Vec2f(t.x, t.w), &tp); break; - case 1: intersects = utility::intersectionPoint(oc, tc, Vec2f(t.x, t.y), Vec2f(t.z, t.y), &tp); break; - case 2: intersects = utility::intersectionPoint(oc, tc, Vec2f(t.z, t.y), Vec2f(t.z, t.w), &tp); break; - case 3: intersects = utility::intersectionPoint(oc, tc, Vec2f(t.x, t.w), Vec2f(t.z, t.w), &tp); break; - } - } - - if (!intersects) - { - tp = tc; - } - - this->setLine(oc.x, oc.y, tc.x, tc.y); - - Vec2f mid = (op + tp) / 2; - m_circle->setPosition(mid); - m_circle->setNumber(number); - - size_t radius = m_circle->getRadius(); - - if (showArrow) - { - Vec2f unit = (tp - op).normalize(); - Vec2f nUnit(-unit.y, unit.x); - Vec2f arrow = mid + unit * (radius + 13); - - Vec2f arrowSide = mid + unit * (radius + 6) + nUnit * 7; - m_arrowRight->setLine(arrow.x, arrow.y, arrowSide.x, arrowSide.y); - - arrowSide -= nUnit * 14; - m_arrowLeft->setLine(arrow.x, arrow.y, arrowSide.x, arrowSide.y); - - m_arrowLeft->show(); - m_arrowRight->show(); - } - else - { - m_arrowLeft->hide(); - m_arrowRight->hide(); - } - - this->setPen(QPen(QBrush(style.color.c_str()), style.width, Qt::SolidLine, Qt::RoundCap)); - - m_circle->setStyle(countStyle.color.fill.c_str(), countStyle.color.text.c_str(), countStyle.color.border.c_str(), 1); - - m_arrowLeft->setPen(QPen(QBrush(countStyle.color.border.c_str()), 1, Qt::SolidLine, Qt::RoundCap)); - m_arrowRight->setPen(QPen(QBrush(countStyle.color.border.c_str()), 1, Qt::SolidLine, Qt::RoundCap)); -} diff --git a/src/lib_gui/qt/graphics/QtStraightLineItem.h b/src/lib_gui/qt/graphics/QtStraightLineItem.h deleted file mode 100644 index 5d9fe8cb..00000000 --- a/src/lib_gui/qt/graphics/QtStraightLineItem.h +++ /dev/null @@ -1,30 +0,0 @@ -#ifndef QT_STRAIGHT_LINE_ITEM_H -#define QT_STRAIGHT_LINE_ITEM_H - -#include - -#include "utility/math/Vector4.h" - -#include "component/view/GraphViewStyle.h" - -class QtCountCircleItem; - -class QtStraightLineItem - : public QGraphicsLineItem -{ -public: - QtStraightLineItem(QGraphicsItem* parent); - virtual ~QtStraightLineItem(); - - void updateLine( - Vec4i ownerRect, Vec4i targetRect, int number, - GraphViewStyle::EdgeStyle style, GraphViewStyle::NodeStyle countStyle, bool showArrow); - -private: - QtCountCircleItem* m_circle; - - QGraphicsLineItem* m_arrowLeft; - QGraphicsLineItem* m_arrowRight; -}; - -#endif // QT_STRAIGHT_LINE_ITEM_H diff --git a/src/lib_gui/qt/view/QtGraphView.cpp b/src/lib_gui/qt/view/QtGraphView.cpp index 6185a7a4..84c6e07b 100644 --- a/src/lib_gui/qt/view/QtGraphView.cpp +++ b/src/lib_gui/qt/view/QtGraphView.cpp @@ -3,16 +3,20 @@ #include #include #include +#include #include -#include #include +#include +#include #include #include +#include #include "component/controller/helper/DummyEdge.h" #include "component/controller/helper/DummyNode.h" #include "component/view/GraphViewStyle.h" #include "settings/ApplicationSettings.h" +#include "utility/messaging/type/MessageActivateTrail.h" #include "utility/messaging/type/MessageDeactivateEdge.h" #include "utility/messaging/type/MessageScrollGraph.h" #include "utility/ResourcePaths.h" @@ -40,6 +44,7 @@ QtGraphView::QtGraphView(ViewLayout* viewLayout) , m_refreshFunctor(std::bind(&QtGraphView::doRefreshView, this)) , m_focusInFunctor(std::bind(&QtGraphView::doFocusIn, this, std::placeholders::_1)) , m_focusOutFunctor(std::bind(&QtGraphView::doFocusOut, this, std::placeholders::_1)) + , m_centerActiveNode(false) , m_scrollToTop(false) , m_restoreScroll(false) , m_isIndexedList(false) @@ -82,6 +87,40 @@ void QtGraphView::initView() connect(view->horizontalScrollBar(), SIGNAL(valueChanged(int)), this, SLOT(scrolled(int))); connect(view->verticalScrollBar(), SIGNAL(valueChanged(int)), this, SLOT(scrolled(int))); + // trail controls + { + m_forwardTrailButton = new QPushButton("<"); + m_backwardTrailButton = new QPushButton(">"); + + connect(m_forwardTrailButton, SIGNAL(clicked()), this, SLOT(clickedForwardTrail())); + connect(m_backwardTrailButton, SIGNAL(clicked()), this, SLOT(clickedBackwardTrail())); + + m_trailDepthSlider = new QSlider(Qt::Horizontal); + m_trailDepthSlider->setMinimum(1); + m_trailDepthSlider->setMaximum(51); + m_trailDepthSlider->setValue(10); + m_trailDepthSlider->setFixedWidth(150); + connect(m_trailDepthSlider, SIGNAL(valueChanged(int)), this, SLOT(trailDepthChanged(int))); + + m_trailDepthLabel = new QLabel("10"); + m_trailDepthLabel->setFixedWidth(30); + + QHBoxLayout* trailLayout = new QHBoxLayout(); + trailLayout->addWidget(m_backwardTrailButton); + trailLayout->addWidget(m_forwardTrailButton); + trailLayout->addStretch(); + trailLayout->addWidget(new QLabel("depth:")); + trailLayout->addWidget(m_trailDepthSlider); + trailLayout->addWidget(m_trailDepthLabel); + + QWidget* trailWidget = new QWidget(widget); + trailWidget->setMinimumWidth(380); + trailWidget->setMinimumHeight(50); + trailWidget->setLayout(trailLayout); + + updateTrailButtons(); + } + doRefreshView(); } @@ -116,7 +155,7 @@ Vec2i QtGraphView::getViewSize() const QtGraphicsView* view = getView(); float zoomFactor = view->getZoomFactor(); - return Vec2i(view->width() / zoomFactor - 60, view->height() / zoomFactor - 60); + return Vec2i((view->width() - 50) / zoomFactor, (view->height() - 100) / zoomFactor); } void QtGraphView::scrollToValues(int xValue, int yValue) @@ -224,6 +263,129 @@ void QtGraphView::scrolled(int) MessageScrollGraph(view->horizontalScrollBar()->value(), view->verticalScrollBar()->value()).dispatch(); } +void QtGraphView::trailDepthChanged(int) +{ + if (m_trailDepthSlider->value() == m_trailDepthSlider->maximum()) + { + m_trailDepthLabel->setText("inf"); + } + else + { + m_trailDepthLabel->setText(QString::number(m_trailDepthSlider->value())); + } +} + +void QtGraphView::clickedBackwardTrail() +{ + activateTrail(false); +} + +void QtGraphView::clickedForwardTrail() +{ + activateTrail(true); +} + +MessageActivateTrail QtGraphView::getMessageActivateTrail(bool forward) +{ + MessageActivateTrail message(0, 0, 0, 0, false); + + QtGraphNodeData* node = nullptr; + if (m_oldActiveNode && (node = dynamic_cast(m_oldActiveNode.get()))) + { + Edge::EdgeTypeMask trailType; + bool horizontalLayout = true; + + switch (node->getData()->getType()) + { + case Node::NODE_CLASS: + case Node::NODE_STRUCT: + case Node::NODE_INTERFACE: + trailType = Edge::EDGE_INHERITANCE; + horizontalLayout = false; + break; + + case Node::NODE_FUNCTION: + case Node::NODE_METHOD: + trailType = Edge::EDGE_CALL | Edge::EDGE_OVERRIDE; + horizontalLayout = true; + break; + + case Node::NODE_FILE: + trailType = Edge::EDGE_INCLUDE; + horizontalLayout = true; + break; + + default: + return message; + } + + Id tokenId = node->getData()->getId(); + Id originId = forward ? tokenId : 0; + Id targetId = forward ? 0 : tokenId; + + int depth = m_trailDepthSlider->value(); + if (depth == m_trailDepthSlider->maximum()) + { + depth = 0; + } + + return MessageActivateTrail(originId, targetId, trailType, depth, horizontalLayout); + } + + return message; +} + +void QtGraphView::activateTrail(bool forward) +{ + MessageActivateTrail message = getMessageActivateTrail(forward); + if (message.trailType) + { + message.dispatch(); + } +} + +void QtGraphView::updateTrailButtons() +{ + MessageActivateTrail message = getMessageActivateTrail(false); + + m_backwardTrailButton->setEnabled(message.trailType); + m_forwardTrailButton->setEnabled(message.trailType); + m_trailDepthSlider->setEnabled(message.trailType); + + if (message.trailType & Edge::EDGE_CALL) + { + m_backwardTrailButton->setToolTip("caller graph"); + m_forwardTrailButton->setToolTip("callee graph"); + + m_backwardTrailButton->setText(">"); + m_forwardTrailButton->setText("<"); + } + else if (message.trailType & Edge::EDGE_INHERITANCE) + { + m_backwardTrailButton->setToolTip("base hierarchy"); + m_forwardTrailButton->setToolTip("derived hierarchy"); + + m_backwardTrailButton->setText("b"); + m_forwardTrailButton->setText("d"); + } + else if (message.trailType & Edge::EDGE_INCLUDE) + { + m_backwardTrailButton->setToolTip("including files hierarchy"); + m_forwardTrailButton->setToolTip("included files hierarchy"); + + m_backwardTrailButton->setText(">"); + m_forwardTrailButton->setText("<"); + } + else + { + m_backwardTrailButton->setToolTip("no hierarchy available for active symbol"); + m_forwardTrailButton->setToolTip("no hierarchy available for active symbol"); + + m_backwardTrailButton->setText(">"); + m_forwardTrailButton->setText("<"); + } +} + void QtGraphView::switchToNewGraphData() { m_oldGraph = m_graph; @@ -231,11 +393,13 @@ void QtGraphView::switchToNewGraphData() for (const std::shared_ptr& node : m_oldNodes) { node->hide(); + node->setParentItem(nullptr); } for (const std::shared_ptr& edge : m_oldEdges) { edge->hide(); + edge->setParentItem(nullptr); } m_oldNodes = m_nodes; @@ -262,24 +426,34 @@ void QtGraphView::switchToNewGraphData() node->hoverEnter(); } - if (m_activeNode) + if (m_activeNodes.size()) { - Vec2i pos = m_activeNode->getPosition(); - Vec2i size = m_activeNode->getSize(); - - QRectF rect(pos.x, pos.y, size.x, size.y); - - if (rect.height() > view->height() - 200) + if (m_centerActiveNode) { - rect.setHeight(view->height() - 200); + Vec2i pos = m_activeNodes.front()->getPosition(); + Vec2i size = m_activeNodes.front()->getSize(); + + QRectF rect(pos.x, pos.y, size.x, size.y); + + if (rect.height() > view->height() - 200) + { + rect.setHeight(view->height() - 200); + } + + view->ensureVisibleAnimated(rect, 100, 100); } - view->ensureVisibleAnimated(rect, 100, 100); - m_activeNode.reset(); + if (m_activeNodes.size() == 1) + { + m_oldActiveNode = m_activeNodes.front(); + } + m_activeNodes.clear(); } // Repaint to make sure all artifacts are removed view->update(); + + updateTrailButtons(); } QtGraphicsView* QtGraphView::getView() const @@ -317,7 +491,10 @@ void QtGraphView::doRebuildGraph( } m_nodes.clear(); - m_activeNode.reset(); + m_activeNodes.clear(); + m_oldActiveNode.reset(); + m_virtualNodeRects.clear(); + for (unsigned int i = 0; i < nodes.size(); i++) { std::shared_ptr node = createNodeRecursive(view, NULL, nodes[i].get(), activeNodeCount > 1); @@ -327,14 +504,17 @@ void QtGraphView::doRebuildGraph( } } - QPointF center = itemsBoundingRect(m_nodes).center(); - Vec2i o = GraphViewStyle::alignOnRaster(Vec2i(center.x(), center.y())); - QPointF offset = QPointF(o.x, o.y); - m_sceneRectOffset = offset - center; - - for (const std::shared_ptr& node : m_nodes) + if (graph->getTrailMode() == Graph::TRAIL_NONE) { - node->setPos(node->pos() - offset); + QPointF center = itemsBoundingRect(m_nodes).center(); + Vec2i o = GraphViewStyle::alignOnRaster(Vec2i(center.x(), center.y())); + QPointF offset = QPointF(o.x, o.y); + m_sceneRectOffset = offset - center; + + for (const std::shared_ptr& node : m_nodes) + { + node->setPos(node->pos() - offset); + } } m_edges.clear(); @@ -344,7 +524,7 @@ void QtGraphView::doRebuildGraph( { if (!edge->data || !edge->data->isType(Edge::EDGE_AGGREGATION)) { - createEdge(view, edge.get(), &visibleEdgeIds); + createEdge(view, edge.get(), &visibleEdgeIds, graph->getTrailMode()); } } for (std::shared_ptr edge : edges) @@ -361,11 +541,7 @@ void QtGraphView::doRebuildGraph( m_graph = graph; } - if (!params.centerActiveNode) - { - m_activeNode.reset(); - } - + m_centerActiveNode = params.centerActiveNode; m_scrollToTop = params.scrollToTop; m_isIndexedList = params.isIndexedList; @@ -381,6 +557,9 @@ void QtGraphView::doRebuildGraph( void QtGraphView::doClear() { + m_oldActiveNode.reset(); + m_activeNodes.clear(); + m_nodes.clear(); m_edges.clear(); @@ -481,9 +660,9 @@ std::shared_ptr QtGraphView::createNodeRecursive( newNode->addComponent(std::make_shared(newNode)); } - if (node->active && !m_activeNode) + if (node->active) { - m_activeNode = newNode; + m_activeNodes.push_back(newNode); } for (unsigned int i = 0; i < node->subNodes.size(); i++) @@ -501,7 +680,7 @@ std::shared_ptr QtGraphView::createNodeRecursive( } std::shared_ptr QtGraphView::createEdge( - QGraphicsView* view, const DummyEdge* edge, std::set* visibleEdgeIds) + QGraphicsView* view, const DummyEdge* edge, std::set* visibleEdgeIds, Graph::TrailMode trailMode) { if (!edge->visible) { @@ -516,6 +695,19 @@ std::shared_ptr QtGraphView::createEdge( std::shared_ptr qtEdge = std::make_shared(owner, target, edge->data, edge->getWeight(), edge->active, edge->getDirection()); + if (trailMode != Graph::TRAIL_NONE) + { + for (const Vec4i& rect : edge->path) + { + m_virtualNodeRects.push_back(QRectF(QPointF(rect.x(), rect.y()), QPointF(rect.z(), rect.w()))); + } + + qtEdge->setIsTrailEdge(edge->path, trailMode == Graph::TRAIL_HORIZONTAL); + } + + qtEdge->updateLine(); + + owner->addOutEdge(qtEdge); target->addInEdge(qtEdge); @@ -558,7 +750,7 @@ std::shared_ptr QtGraphView::createAggregationEdge( return NULL; } - return createEdge(view, edge, visibleEdgeIds); + return createEdge(view, edge, visibleEdgeIds, Graph::TRAIL_NONE); } QRectF QtGraphView::itemsBoundingRect(const std::list>& items) const @@ -573,7 +765,14 @@ QRectF QtGraphView::itemsBoundingRect(const std::list>& items) const { - return itemsBoundingRect(items).adjusted(-25, -25, 25, 25).translated(m_sceneRectOffset); + QRectF sceneRect = itemsBoundingRect(items); + + for (const QRectF& rect : m_virtualNodeRects) + { + sceneRect |= rect; + } + + return sceneRect.adjusted(-25, -75, 25, 25).translated(m_sceneRectOffset); } void QtGraphView::compareNodesRecursive( @@ -804,16 +1003,11 @@ void QtGraphView::doFocusOut(const std::vector& tokenIds) if (node && node->isDataNode()) { node->focusOut(); - continue; - } - - for (std::shared_ptr edge : m_oldEdges) - { - if (edge->getData() && edge->getData()->getId() == tokenId) - { - edge->focusOut(); - break; - } } } + + for (std::shared_ptr edge : m_oldEdges) + { + edge->focusOut(); + } } diff --git a/src/lib_gui/qt/view/QtGraphView.h b/src/lib_gui/qt/view/QtGraphView.h index d2dfe606..88db218a 100644 --- a/src/lib_gui/qt/view/QtGraphView.h +++ b/src/lib_gui/qt/view/QtGraphView.h @@ -7,14 +7,19 @@ #include #include "component/view/GraphView.h" +#include "data/graph/Graph.h" #include "qt/utility/QtScrollSpeedChangeListener.h" #include "qt/utility/QtThreadedFunctor.h" #include "utility/types.h" struct DummyEdge; struct DummyNode; +class MessageActivateTrail; +class QLabel; class QMouseEvent; +class QPushButton; class QSequentialAnimationGroup; +class QSlider; class QtGraphEdge; class QtGraphicsView; class QtGraphNode; @@ -57,7 +62,15 @@ private slots: void pressedCharacterKey(QChar c); void scrolled(int); + void trailDepthChanged(int); + void clickedBackwardTrail(); + void clickedForwardTrail(); + private: + MessageActivateTrail getMessageActivateTrail(bool forward); + void activateTrail(bool forward); + void updateTrailButtons(); + void switchToNewGraphData(); QtGraphicsView* getView() const; @@ -78,7 +91,8 @@ private: std::shared_ptr createNodeRecursive( QGraphicsView* view, std::shared_ptr parentNode, const DummyNode* node, bool multipleActive); - std::shared_ptr createEdge(QGraphicsView* view, const DummyEdge* edge, std::set* visibleEdgeIds); + std::shared_ptr createEdge( + QGraphicsView* view, const DummyEdge* edge, std::set* visibleEdgeIds, Graph::TrailMode trailMode); std::shared_ptr createAggregationEdge( QGraphicsView* view, const DummyEdge* edge, std::set* visibleEdgeIds); @@ -115,7 +129,10 @@ private: std::list> m_nodes; std::list> m_oldNodes; - std::shared_ptr m_activeNode; + std::vector> m_activeNodes; + std::shared_ptr m_oldActiveNode; + + bool m_centerActiveNode; bool m_scrollToTop; bool m_restoreScroll; Vec2i m_scrollValues; @@ -126,6 +143,13 @@ private: QtScrollSpeedChangeListener m_scrollSpeedChangeListenerHorizontal; QtScrollSpeedChangeListener m_scrollSpeedChangeListenerVertical; + + QPushButton* m_forwardTrailButton; + QPushButton* m_backwardTrailButton; + QSlider* m_trailDepthSlider; + QLabel* m_trailDepthLabel; + + std::vector m_virtualNodeRects; }; #endif // QT_GRAPH_VIEW_H diff --git a/src/lib_gui/qt/view/graphElements/QtGraphEdge.cpp b/src/lib_gui/qt/view/graphElements/QtGraphEdge.cpp index 0eb8ef79..e8be8ad3 100644 --- a/src/lib_gui/qt/view/graphElements/QtGraphEdge.cpp +++ b/src/lib_gui/qt/view/graphElements/QtGraphEdge.cpp @@ -1,12 +1,14 @@ #include "qt/view/graphElements/QtGraphEdge.h" #include +#include #include "component/view/GraphViewStyle.h" #include "data/graph/Edge.h" #include "data/graph/token_component/TokenComponentAggregation.h" -#include "qt/graphics/QtAngledLineItem.h" -#include "qt/graphics/QtStraightLineItem.h" +#include "qt/graphics/QtLineItemAngled.h" +#include "qt/graphics/QtLineItemBezier.h" +#include "qt/graphics/QtLineItemStraight.h" #include "qt/view/graphElements/QtGraphNode.h" #include "utility/messaging/type/MessageActivateEdge.h" #include "utility/messaging/type/MessageFocusIn.h" @@ -30,10 +32,14 @@ QtGraphEdge::QtGraphEdge( , m_isActive(isActive) , m_fromActive(false) , m_toActive(false) + , m_isFocused(false) , m_weight(weight) , m_direction(direction) + , m_isTrailEdge(false) + , m_isHorizontalTrail(false) , m_mousePos(0.0f, 0.0f) , m_mouseMoved(false) + , m_willDispatchMessageFocusIn(false) { if (m_direction == TokenComponentAggregation::DIRECTION_BACKWARD) { @@ -42,8 +48,6 @@ QtGraphEdge::QtGraphEdge( m_fromActive = m_owner.lock()->getIsActive(); m_toActive = m_target.lock()->getIsActive(); - - this->updateLine(); } QtGraphEdge::~QtGraphEdge() @@ -86,30 +90,95 @@ void QtGraphEdge::updateLine() type = Edge::EDGE_AGGREGATION; } - GraphViewStyle::EdgeStyle style = GraphViewStyle::getStyleForEdgeType(type, m_isActive, false); + QString toolTip = Edge::getReadableTypeString(type).c_str(); + if (type == Edge::EDGE_AGGREGATION) + { + toolTip += ": " + QString::number(m_weight) + " edge"; + if (m_weight != 1) + { + toolTip += "s"; + } + } - if (style.isStraight) + GraphViewStyle::EdgeStyle style = GraphViewStyle::getStyleForEdgeType(type, m_isActive | m_isFocused, false, m_isTrailEdge); + + if (m_isTrailEdge) { if (!m_child) { - m_child = new QtStraightLineItem(this); + m_child = new QGraphicsLineItem(this); + } + else + { + for (QGraphicsItem* item : m_child->childItems()) + { + item->hide(); + item->setParentItem(nullptr); + } + } + + style.originOffset.y() = 0; + style.targetOffset.y() = 0; + + Vec4i ownerRect = owner->getBoundingRect(); + Vec4i ownerParentRect = owner->getParentBoundingRect(); + + QtLineItemBase::Route route = + m_isHorizontalTrail ? QtLineItemBase::ROUTE_HORIZONTAL : QtLineItemBase::ROUTE_VERTICAL; + + for (const Vec4i& rect : m_path) + { + QtLineItemBezier* bezier = new QtLineItemBezier(m_child); + bezier->updateLine(ownerRect, rect, ownerParentRect, rect, style, m_weight, false); + bezier->setRoute(route); + bezier->setPivot(QtLineItemBase::PIVOT_MIDDLE); + bezier->setToolTip(toolTip); + + QtLineItemStraight* line = new QtLineItemStraight(m_child); + line->setToolTip(toolTip); + if (route == QtLineItemBase::ROUTE_HORIZONTAL) + { + line->updateLine(Vec2i(rect.x(), (rect.y() + rect.w()) / 2), Vec2i(rect.z(), (rect.y() + rect.w()) / 2), style); + } + else + { + line->updateLine(Vec2i((rect.x() + rect.z()) / 2, rect.y()), Vec2i((rect.x() + rect.z()) / 2, rect.w()), style); + } + + ownerRect = rect; + ownerParentRect = rect; } bool showArrow = m_direction != TokenComponentAggregation::DIRECTION_NONE; - GraphViewStyle::NodeStyle countStyle = GraphViewStyle::getStyleOfCountCircle(); + QtLineItemBezier* bezier = new QtLineItemBezier(m_child); + bezier->updateLine( + ownerRect, target->getBoundingRect(), ownerParentRect, target->getParentBoundingRect(), + style, m_weight, showArrow); + bezier->setRoute(route); + bezier->setPivot(QtLineItemBase::PIVOT_MIDDLE); + bezier->setToolTip(toolTip); - dynamic_cast(m_child)->updateLine( - owner->getBoundingRect(), target->getBoundingRect(), m_weight, style, countStyle, showArrow); + if (owner->getLastParent() == target->getLastParent()) + { + if (ownerRect.y() < target->getBoundingRect().y()) + { + bezier->setOnBack(true); + } + else + { + bezier->setOnFront(true); + } + } } else { if (!m_child) { - m_child = new QtAngledLineItem(this); + m_child = new QtLineItemAngled(this); } - QtAngledLineItem* child = dynamic_cast(m_child); + QtLineItemAngled* child = dynamic_cast(m_child); if (m_fromActive && owner->getLastParent() == target->getLastParent()) { @@ -130,13 +199,13 @@ void QtGraphEdge::updateLine() (type != Edge::EDGE_AGGREGATION || owner.get() != owner->getLastParent() || target.get() != target->getLastParent())) { - child->setRoute(QtAngledLineItem::ROUTE_HORIZONTAL); + child->setRoute(QtLineItemBase::ROUTE_HORIZONTAL); } bool showArrow = true; if (type == Edge::EDGE_AGGREGATION) { - child->setPivot(QtAngledLineItem::PIVOT_MIDDLE); + child->setPivot(QtLineItemBase::PIVOT_MIDDLE); showArrow = m_direction != TokenComponentAggregation::DIRECTION_NONE; } @@ -145,20 +214,11 @@ void QtGraphEdge::updateLine() owner->getBoundingRect(), target->getBoundingRect(), owner->getParentBoundingRect(), target->getParentBoundingRect(), style, m_weight, showArrow); + + child->setToolTip(toolTip); } - QString toolTip = Edge::getReadableTypeString(type).c_str(); - if (type == Edge::EDGE_AGGREGATION) - { - toolTip += ": " + QString::number(m_weight) + " edge"; - if (m_weight != 1) - { - toolTip += "s"; - } - } - m_child->setToolTip(toolTip); - - this->setZValue(style.zValue); // Used to draw edges always on top of nodes. + this->setZValue(style.zValue); } bool QtGraphEdge::getIsActive() const @@ -177,6 +237,12 @@ void QtGraphEdge::setIsActive(bool isActive) void QtGraphEdge::onClick() { + if (isTrailEdge()) + { + setIsActive(!getIsActive()); + return; + } + if (!getData()) { std::weak_ptr node = @@ -204,14 +270,20 @@ void QtGraphEdge::onClick() void QtGraphEdge::focusIn() { - bool isActive = m_isActive; - this->setIsActive(true); - m_isActive = isActive; + if (!m_isFocused) + { + m_isFocused = true; + updateLine(); + } } void QtGraphEdge::focusOut() { - updateLine(); + if (m_isFocused) + { + m_isFocused = false; + updateLine(); + } } void QtGraphEdge::mousePressEvent(QGraphicsSceneMouseEvent* event) @@ -246,7 +318,11 @@ void QtGraphEdge::hoverEnterEvent(QGraphicsSceneHoverEvent* event) return; } - MessageFocusIn(std::vector(1, getData()->getId())).dispatch(); + if (!m_willDispatchMessageFocusIn) + { + QTimer::singleShot(50, this, SLOT(dispatchMessageFocusIn())); + m_willDispatchMessageFocusIn = true; + } } void QtGraphEdge::hoverLeaveEvent(QGraphicsSceneHoverEvent* event) @@ -257,9 +333,24 @@ void QtGraphEdge::hoverLeaveEvent(QGraphicsSceneHoverEvent* event) return; } + if (m_willDispatchMessageFocusIn) + { + m_willDispatchMessageFocusIn = false; + return; + } + MessageFocusOut(std::vector(1, getData()->getId())).dispatch(); } +void QtGraphEdge::dispatchMessageFocusIn() +{ + if (m_willDispatchMessageFocusIn) + { + m_willDispatchMessageFocusIn = false; + MessageFocusIn(std::vector(1, getData()->getId())).dispatch(); + } +} + void QtGraphEdge::setDirection(TokenComponentAggregation::Direction direction) { if (m_direction != direction) @@ -268,3 +359,15 @@ void QtGraphEdge::setDirection(TokenComponentAggregation::Direction direction) updateLine(); } } + +bool QtGraphEdge::isTrailEdge() const +{ + return m_isTrailEdge; +} + +void QtGraphEdge::setIsTrailEdge(std::vector path, bool horizontal) +{ + m_path = path; + m_isTrailEdge = true; + m_isHorizontalTrail = horizontal; +} diff --git a/src/lib_gui/qt/view/graphElements/QtGraphEdge.h b/src/lib_gui/qt/view/graphElements/QtGraphEdge.h index d886bbe8..6f0d2a3d 100644 --- a/src/lib_gui/qt/view/graphElements/QtGraphEdge.h +++ b/src/lib_gui/qt/view/graphElements/QtGraphEdge.h @@ -6,6 +6,7 @@ #include #include "utility/math/Vector2.h" +#include "utility/math/Vector4.h" #include "data/graph/token_component/TokenComponentAggregation.h" @@ -47,6 +48,9 @@ public: void setDirection(TokenComponentAggregation::Direction direction); + bool isTrailEdge() const; + void setIsTrailEdge(std::vector path, bool horizontal); + protected: virtual void mousePressEvent(QGraphicsSceneMouseEvent* event); virtual void mouseMoveEvent(QGraphicsSceneMouseEvent* event); @@ -55,6 +59,9 @@ protected: virtual void hoverEnterEvent(QGraphicsSceneHoverEvent* event); virtual void hoverLeaveEvent(QGraphicsSceneHoverEvent* event); +private slots: + void dispatchMessageFocusIn(); + private: const Edge* m_data; @@ -66,12 +73,21 @@ private: bool m_isActive; bool m_fromActive; bool m_toActive; + + bool m_isFocused; + size_t m_weight; TokenComponentAggregation::Direction m_direction; + bool m_isTrailEdge; + bool m_isHorizontalTrail; + std::vector m_path; + Vec2i m_mousePos; bool m_mouseMoved; + + bool m_willDispatchMessageFocusIn; }; #endif // QT_GRAPH_EDGE_H diff --git a/src/lib_gui/qt/view/graphElements/QtGraphNode.cpp b/src/lib_gui/qt/view/graphElements/QtGraphNode.cpp index a35923fc..0af2b6b9 100644 --- a/src/lib_gui/qt/view/graphElements/QtGraphNode.cpp +++ b/src/lib_gui/qt/view/graphElements/QtGraphNode.cpp @@ -213,12 +213,34 @@ void QtGraphNode::hoverEnter() void QtGraphNode::focusIn() { m_isHovering = true; + + forEachEdge( + [](QtGraphEdge* edge) + { + if (edge->isTrailEdge()) + { + edge->focusIn(); + } + } + ); + updateStyle(); } void QtGraphNode::focusOut() { m_isHovering = false; + + forEachEdge( + [](QtGraphEdge* edge) + { + if (edge->isTrailEdge()) + { + edge->focusOut(); + } + } + ); + updateStyle(); } @@ -353,11 +375,11 @@ void QtGraphNode::mouseReleaseEvent(QGraphicsSceneMouseEvent* event) } } -void QtGraphNode::notifyEdgesAfterMove() +void QtGraphNode::forEachEdge(std::function func) { for (const std::shared_ptr& edge : m_outEdges) { - edge->updateLine(); + func(edge.get()); } for (const std::weak_ptr& e : m_inEdges) @@ -365,9 +387,19 @@ void QtGraphNode::notifyEdgesAfterMove() std::shared_ptr edge = e.lock(); if (edge) { - edge->updateLine(); + func(edge.get()); } } +} + +void QtGraphNode::notifyEdgesAfterMove() +{ + forEachEdge( + [](QtGraphEdge* edge) + { + edge->updateLine(); + } + ); for (const std::shared_ptr& node : m_subNodes) { diff --git a/src/lib_gui/qt/view/graphElements/QtGraphNode.h b/src/lib_gui/qt/view/graphElements/QtGraphNode.h index ba67a616..3a7a535a 100644 --- a/src/lib_gui/qt/view/graphElements/QtGraphNode.h +++ b/src/lib_gui/qt/view/graphElements/QtGraphNode.h @@ -1,6 +1,8 @@ #ifndef QT_GRAPH_NODE_H #define QT_GRAPH_NODE_H +#include + #include #include "utility/math/Vector4.h" @@ -93,6 +95,8 @@ protected: virtual void mouseMoveEvent(QGraphicsSceneMouseEvent* event); virtual void mouseReleaseEvent(QGraphicsSceneMouseEvent* event); + void forEachEdge(std::function func); + void notifyEdgesAfterMove(); void setStyle(const GraphViewStyle::NodeStyle& style);