ui: Show call graphs, inheritance trees and include trees for active symbol (issue 249 337)
* Added TrailLayouter with Sugiyama dependency layouting * Layouter supports horizontal and vertical layouts * Added basic ui to graph view for showing incoming and outgoing paths * Added depth slider, with infinity at maximum * Disable trail ui when not available * Added trail messages to undo stack and only use last one when undoing * Added QtGraphLineBezier for trail graphs and refactored other line types * Disabled edge activation for trail layouts, shown only visually * Highlight all connected edges when hovering a node in trail layout * Use different color when highlighting call edges for better contrast * Fixed BucketLayouter issue when edge does not leave node * Increased click area for angled edges bug id = 249 337 fortune cookie message = Answer just what the heart prompts to you.
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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> 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<Id>(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<Id>& tokenId
|
||||
|
||||
m_dummyEdges.clear();
|
||||
m_dummyGraphNodes.clear();
|
||||
m_topLevelAncestorIds.clear();
|
||||
|
||||
std::set<Id> addedNodes;
|
||||
std::vector<std::shared_ptr<DummyNode>> dummyNodes;
|
||||
@@ -308,14 +356,14 @@ void GraphController::createDummyGraphForTokenIds(const std::vector<Id>& 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<std::shared_ptr<DummyNode>> GraphController::createDummyNodeTopDown(Node* node)
|
||||
std::vector<std::shared_ptr<DummyNode>> GraphController::createDummyNodeTopDown(Node* node, Id ancestorId)
|
||||
{
|
||||
std::vector<std::shared_ptr<DummyNode>> nodes;
|
||||
|
||||
std::shared_ptr<DummyNode> result = std::make_shared<DummyNode>();
|
||||
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<std::shared_ptr<DummyNode>> 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<std::shared_ptr<DummyNode>> 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<Id>& activeTokenIds
|
||||
}
|
||||
}
|
||||
|
||||
bool GraphController::setActive(const std::vector<Id>& activeTokenIds)
|
||||
bool GraphController::setActive(const std::vector<Id>& activeTokenIds, bool showAllEdges)
|
||||
{
|
||||
TRACE();
|
||||
|
||||
@@ -513,7 +563,7 @@ bool GraphController::setActive(const std::vector<Id>& 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<Id>& activeTokenI
|
||||
{
|
||||
TRACE();
|
||||
|
||||
setVisibility(setActive(activeTokenIds));
|
||||
setVisibility(setActive(activeTokenIds, false));
|
||||
}
|
||||
|
||||
void GraphController::setNodeActiveRecursive(DummyNode* node, const std::vector<Id>& 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<std::shared_ptr<DummyEdge>>());
|
||||
grid.layoutBuckets();
|
||||
|
||||
buildGraph(message, false);
|
||||
buildGraph(message, false, true, false);
|
||||
|
||||
getView()->focusTokenIds(focusedTokenIds);
|
||||
}
|
||||
|
||||
@@ -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<MessageActivateAll>
|
||||
, public MessageListener<MessageActivateTokens>
|
||||
, public MessageListener<MessageActivateTrail>
|
||||
, public MessageListener<MessageColorSchemeTest>
|
||||
, public MessageListener<MessageFlushUpdates>
|
||||
, public MessageListener<MessageFocusIn>
|
||||
@@ -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<Id>& tokenIds, const std::shared_ptr<Graph> graph);
|
||||
void createDummyGraphForTokenIdsAndSetActiveAndVisibility(
|
||||
const std::vector<Id>& tokenIds, const std::shared_ptr<Graph> graph);
|
||||
std::vector<std::shared_ptr<DummyNode>> createDummyNodeTopDown(Node* node);
|
||||
std::vector<std::shared_ptr<DummyNode>> createDummyNodeTopDown(Node* node, Id ancestorId);
|
||||
|
||||
std::vector<Id> getExpandedNodeIds() const;
|
||||
void setExpandedNodeIds(const std::vector<Id>& nodeIds);
|
||||
void autoExpandActiveNode(const std::vector<Id>& activeTokenIds);
|
||||
|
||||
bool setActive(const std::vector<Id>& activeTokenIds);
|
||||
bool setActive(const std::vector<Id>& activeTokenIds, bool showAllEdges);
|
||||
void setVisibility(bool noActive);
|
||||
void setActiveAndVisibility(const std::vector<Id>& activeTokenIds);
|
||||
void setNodeActiveRecursive(DummyNode* node, const std::vector<Id>& 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<void(DummyNode*)> func);
|
||||
void forEachDummyEdge(std::function<void(DummyEdge*)> func);
|
||||
@@ -126,6 +129,8 @@ private:
|
||||
std::vector<Id> m_activeEdgeIds;
|
||||
|
||||
std::shared_ptr<Graph> m_graph;
|
||||
|
||||
std::map<Id, Id> m_topLevelAncestorIds;
|
||||
};
|
||||
|
||||
#endif // GRAPH_CONTROLLER_H
|
||||
|
||||
@@ -77,6 +77,19 @@ void UndoRedoController::handleMessage(MessageActivateTokens* message)
|
||||
processCommand(command);
|
||||
}
|
||||
|
||||
void UndoRedoController::handleMessage(MessageActivateTrail* message)
|
||||
{
|
||||
if (sameMessageTypeAsLast(message) &&
|
||||
static_cast<MessageActivateTrail*>(lastMessage())->originId == message->originId &&
|
||||
static_cast<MessageActivateTrail*>(lastMessage())->targetId == message->targetId)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Command command(std::make_shared<MessageActivateTrail>(*message), Command::ORDER_ADAPT, true);
|
||||
processCommand(command);
|
||||
}
|
||||
|
||||
void UndoRedoController::handleMessage(MessageChangeFileView* message)
|
||||
{
|
||||
Command command(std::make_shared<MessageChangeFileView>(*message), Command::ORDER_VIEW);
|
||||
@@ -320,9 +333,21 @@ void UndoRedoController::replayCommands(std::list<Command>::iterator it)
|
||||
std::vector<std::list<Command>::iterator> viewCommands;
|
||||
bool keepsContent = true;
|
||||
|
||||
std::map<std::string, std::list<Command>::iterator> lastOfType;
|
||||
std::list<Command>::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<Command>::iterator it)
|
||||
{
|
||||
keepsContent = false;
|
||||
}
|
||||
|
||||
if (it->order != Command::ORDER_VIEW)
|
||||
{
|
||||
viewCommands.clear();
|
||||
}
|
||||
}
|
||||
else
|
||||
else if (it->order == Command::ORDER_VIEW)
|
||||
{
|
||||
viewCommands.push_back(it);
|
||||
}
|
||||
|
||||
@@ -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<MessageActivateAll>
|
||||
, public MessageListener<MessageActivateLocalSymbols>
|
||||
, public MessageListener<MessageActivateTokens>
|
||||
, public MessageListener<MessageActivateTrail>
|
||||
, public MessageListener<MessageChangeFileView>
|
||||
, public MessageListener<MessageDeactivateEdge>
|
||||
, public MessageListener<MessageFinishedParsing>
|
||||
@@ -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);
|
||||
|
||||
@@ -154,7 +154,7 @@ void BucketLayouter::createBuckets(
|
||||
std::shared_ptr<DummyNode> target = findTopMostDummyNodeRecursive(nodes, edge->targetId, nullptr);
|
||||
|
||||
bool removeEdge = false;
|
||||
if (!owner || !target)
|
||||
if (!owner || !target || owner == target)
|
||||
{
|
||||
removeEdge = true;
|
||||
}
|
||||
|
||||
@@ -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<Vec4i> path;
|
||||
|
||||
// BundleEdge
|
||||
int weight;
|
||||
TokenComponentAggregation::Direction direction;
|
||||
|
||||
@@ -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<std::shared_ptr<DummyNode>> subNodes;
|
||||
|
||||
@@ -0,0 +1,573 @@
|
||||
#include "component/controller/helper/TrailLayouter.h"
|
||||
|
||||
#include <iostream>
|
||||
|
||||
TrailLayouter::TrailLayouter(LayoutDirection dir)
|
||||
: m_direction(dir)
|
||||
{
|
||||
}
|
||||
|
||||
void TrailLayouter::layoutGraph(
|
||||
std::vector<std::shared_ptr<DummyNode>>& dummyNodes,
|
||||
const std::vector<std::shared_ptr<DummyEdge>>& dummyEdges,
|
||||
const std::map<Id, Id>& topLevelAncestorIds)
|
||||
{
|
||||
buildGraph(dummyNodes, dummyEdges, topLevelAncestorIds);
|
||||
|
||||
makeAcyclic();
|
||||
|
||||
assignLongestPathLevels();
|
||||
assignRemainingLevels();
|
||||
|
||||
addVirtualNodes();
|
||||
|
||||
buildColumns();
|
||||
reduceEdgeCrossings();
|
||||
layout();
|
||||
|
||||
retrievePositions(topLevelAncestorIds);
|
||||
|
||||
// print();
|
||||
}
|
||||
|
||||
void TrailLayouter::buildGraph(
|
||||
std::vector<std::shared_ptr<DummyNode>>& dummyNodes,
|
||||
const std::vector<std::shared_ptr<DummyEdge>>& dummyEdges,
|
||||
const std::map<Id, Id>& topLevelAncestorIds)
|
||||
{
|
||||
for (const std::shared_ptr<DummyNode> dummyNode : dummyNodes)
|
||||
{
|
||||
addNode(dummyNode);
|
||||
}
|
||||
|
||||
for (const std::shared_ptr<DummyEdge> 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> dummyEdge : dummyEdges)
|
||||
{
|
||||
if (dummyEdge->data && dummyEdge->data->isType(Edge::EDGE_OVERRIDE | Edge::EDGE_INHERITANCE))
|
||||
{
|
||||
addEdge(dummyEdge, topLevelAncestorIds);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TrailLayouter::makeAcyclic()
|
||||
{
|
||||
std::set<TrailEdge*> edgesToSwitch;
|
||||
for (std::shared_ptr<TrailNode> 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<TrailEdge*> edgesToSwitch = node->incomingEdges;
|
||||
for (TrailEdge* edge : edgesToSwitch)
|
||||
{
|
||||
switchEdge(edge);
|
||||
}
|
||||
|
||||
makeAcyclicRecursive(node, std::set<TrailNode*>());
|
||||
}
|
||||
}
|
||||
|
||||
void TrailLayouter::makeAcyclicRecursive(TrailNode* node, std::set<TrailNode*> predecessors)
|
||||
{
|
||||
predecessors.insert(node);
|
||||
|
||||
std::set<TrailEdge*> 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<TrailNode*> nodes(m_rootNodes.begin(), m_rootNodes.end());
|
||||
std::map<TrailNode*, TrailNode*> predecessorNodes;
|
||||
|
||||
int level = 0;
|
||||
|
||||
while (nodes.size())
|
||||
{
|
||||
std::set<TrailNode*> 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<TrailNode*> newNodes;
|
||||
|
||||
for (TrailNode* node : nodes)
|
||||
{
|
||||
node->level = level;
|
||||
|
||||
if (level > 0)
|
||||
{
|
||||
newNodes.insert(predecessorNodes[node]);
|
||||
}
|
||||
}
|
||||
|
||||
nodes = newNodes;
|
||||
level--;
|
||||
}
|
||||
}
|
||||
|
||||
void TrailLayouter::assignRemainingLevels()
|
||||
{
|
||||
std::set<TrailNode*> nodes(m_rootNodes.begin(), m_rootNodes.end());
|
||||
while (nodes.size())
|
||||
{
|
||||
std::set<TrailNode*> 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<std::shared_ptr<TrailEdge>> newEdges;
|
||||
|
||||
for (std::shared_ptr<TrailEdge> edge : m_allEdges)
|
||||
{
|
||||
for (int i = edge->origin->level + 1; i < edge->target->level; i++)
|
||||
{
|
||||
std::shared_ptr<TrailNode> virtualNode = std::make_shared<TrailNode>();
|
||||
virtualNode->id = 0;
|
||||
virtualNode->name = "<virtual>";
|
||||
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<TrailEdge> virtualEdge = std::make_shared<TrailEdge>();
|
||||
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<TrailNode> node : m_allNodes)
|
||||
{
|
||||
int level = node->level + 1;
|
||||
for (int i = m_nodesPerCol.size(); i <= level; i++)
|
||||
{
|
||||
m_nodesPerCol.push_back(std::vector<TrailNode*>());
|
||||
}
|
||||
|
||||
m_nodesPerCol[level].push_back(node.get());
|
||||
}
|
||||
}
|
||||
|
||||
void TrailLayouter::reduceEdgeCrossings()
|
||||
{
|
||||
for (size_t i = 1; i < m_nodesPerCol.size(); i++)
|
||||
{
|
||||
std::vector<TrailNode*> nodes = m_nodesPerCol[i];
|
||||
|
||||
bool usePredecessors = true;
|
||||
std::vector<TrailNode*> 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<TrailNode*, size_t> neighborIndices;
|
||||
for (size_t j = 0; j < neighbors.size(); j++)
|
||||
{
|
||||
neighborIndices.emplace(neighbors[j], j);
|
||||
}
|
||||
|
||||
std::multimap<float, TrailNode*> 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<float, TrailNode*> 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<int> widthsPerCol;
|
||||
std::vector<int> 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<TrailNode*>& 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<TrailNode*>& 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<TrailNode*> 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<Id, Id>& topLevelAncestorIds)
|
||||
{
|
||||
for (std::shared_ptr<TrailNode> node : m_allNodes)
|
||||
{
|
||||
if (node->dummyNode)
|
||||
{
|
||||
node->dummyNode->position = node->pos;
|
||||
}
|
||||
}
|
||||
|
||||
for (std::shared_ptr<TrailEdge> 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<TrailNode> 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<TrailEdge> 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>& dummyNode)
|
||||
{
|
||||
std::shared_ptr<TrailNode> node = std::make_shared<TrailNode>();
|
||||
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> dummyEdge, const std::map<Id, Id>& topLevelAncestorIds)
|
||||
{
|
||||
std::shared_ptr<TrailEdge> edge = std::make_shared<TrailEdge>();
|
||||
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<TrailEdge> 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;
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
#ifndef GRAPH_LAYOUTER_H
|
||||
#define GRAPH_LAYOUTER_H
|
||||
|
||||
#include <map>
|
||||
#include <set>
|
||||
#include <vector>
|
||||
|
||||
#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<std::shared_ptr<DummyNode>>& dummyNodes,
|
||||
const std::vector<std::shared_ptr<DummyEdge>>& dummyEdges,
|
||||
const std::map<Id, Id>& topLevelAncestorIds);
|
||||
|
||||
private:
|
||||
struct TrailEdge;
|
||||
|
||||
struct TrailNode
|
||||
{
|
||||
Id id;
|
||||
int level;
|
||||
std::string name;
|
||||
|
||||
Vec2i pos;
|
||||
Vec2i size;
|
||||
|
||||
std::set<TrailEdge*> incomingEdges;
|
||||
std::set<TrailEdge*> outgoingEdges;
|
||||
|
||||
DummyNode* dummyNode;
|
||||
};
|
||||
|
||||
struct TrailEdge
|
||||
{
|
||||
Id id;
|
||||
TrailNode* origin;
|
||||
TrailNode* target;
|
||||
|
||||
std::vector<TrailNode*> virtualNodes;
|
||||
|
||||
std::vector<DummyEdge*> dummyEdges;
|
||||
};
|
||||
|
||||
void buildGraph(
|
||||
std::vector<std::shared_ptr<DummyNode>>& dummyNodes,
|
||||
const std::vector<std::shared_ptr<DummyEdge>>& dummyEdges,
|
||||
const std::map<Id, Id>& topLevelAncestorIds);
|
||||
|
||||
void makeAcyclic();
|
||||
void makeAcyclicRecursive(TrailNode* node, std::set<TrailNode*> predecessors);
|
||||
|
||||
void assignLongestPathLevels();
|
||||
void assignRemainingLevels();
|
||||
|
||||
void addVirtualNodes();
|
||||
void buildColumns();
|
||||
void reduceEdgeCrossings();
|
||||
|
||||
void layout();
|
||||
void moveNodesToAveragePosition(std::vector<TrailNode*> nodes);
|
||||
void retrievePositions(const std::map<Id, Id>& topLevelAncestorIds);
|
||||
|
||||
void print();
|
||||
|
||||
void addNode(const std::shared_ptr<DummyNode>& dummyNode);
|
||||
void addEdge(const std::shared_ptr<DummyEdge> dummyEdge, const std::map<Id, Id>& topLevelAncestorIds);
|
||||
void switchEdge(TrailEdge* edge);
|
||||
|
||||
bool horizontalLayout() const;
|
||||
bool invertedLayout() const;
|
||||
|
||||
LayoutDirection m_direction;
|
||||
|
||||
std::vector<std::shared_ptr<TrailNode>> m_allNodes;
|
||||
std::vector<std::shared_ptr<TrailEdge>> m_allEdges;
|
||||
|
||||
std::map<Id, TrailNode*> m_nodesById;
|
||||
std::vector<TrailNode*> m_rootNodes;
|
||||
|
||||
std::vector<std::vector<TrailNode*>> m_nodesPerCol;
|
||||
};
|
||||
|
||||
#endif // GRAPH_LAYOUTER_H
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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<NodeBookmark> PersistentStorage::getAllNodeBookmarks() const
|
||||
std::unordered_map<Id, std::vector<Id>> 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<NodeBookmark> nodeBookmarks;
|
||||
@@ -272,7 +277,12 @@ std::vector<EdgeBookmark> PersistentStorage::getAllEdgeBookmarks() const
|
||||
|
||||
std::vector<EdgeBookmark> edgeBookmarks;
|
||||
|
||||
Cache<std::string, Id> nodeIdCache([&](std::string serializedNodeName){ return m_sqliteIndexStorage.getNodeBySerializedName(serializedNodeName).id; });
|
||||
Cache<std::string, Id> 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<EdgeBookmark> 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<void(
|
||||
}
|
||||
}
|
||||
|
||||
void PersistentStorage::forEachSourceLocation(std::function<void(const Id /*id*/, const StorageSourceLocation& /*data*/)> callback) const
|
||||
void PersistentStorage::forEachSourceLocation(
|
||||
std::function<void(const Id /*id*/, const StorageSourceLocation& /*data*/)> callback) const
|
||||
{
|
||||
for (StorageSourceLocation& sourceLocation: m_sqliteIndexStorage.getAll<StorageSourceLocation>())
|
||||
{
|
||||
@@ -767,7 +779,8 @@ std::vector<SearchMatch> PersistentStorage::getAutocompletionMatches(const std::
|
||||
return matches;
|
||||
}
|
||||
|
||||
std::vector<SearchMatch> PersistentStorage::getAutocompletionSymbolMatches(const std::string& query, size_t maxResultsCount) const
|
||||
std::vector<SearchMatch> PersistentStorage::getAutocompletionSymbolMatches(
|
||||
const std::string& query, size_t maxResultsCount) const
|
||||
{
|
||||
// search in indices
|
||||
std::vector<SearchResult> results = m_symbolIndex.search(query, maxResultsCount, maxResultsCount);
|
||||
@@ -985,7 +998,8 @@ std::shared_ptr<Graph> PersistentStorage::getGraphForAll() const
|
||||
return graph;
|
||||
}
|
||||
|
||||
std::shared_ptr<Graph> PersistentStorage::getGraphForActiveTokenIds(const std::vector<Id>& tokenIds, bool* isActiveNamespace) const
|
||||
std::shared_ptr<Graph> PersistentStorage::getGraphForActiveTokenIds(
|
||||
const std::vector<Id>& tokenIds, bool* isActiveNamespace) const
|
||||
{
|
||||
TRACE();
|
||||
|
||||
@@ -1108,6 +1122,71 @@ std::shared_ptr<Graph> PersistentStorage::getGraphForActiveTokenIds(const std::v
|
||||
return g;
|
||||
}
|
||||
|
||||
std::shared_ptr<Graph> PersistentStorage::getGraphForTrail(
|
||||
Id originId, Id targetId, Edge::EdgeTypeMask trailType, size_t depth) const
|
||||
{
|
||||
TRACE();
|
||||
|
||||
std::set<Id> nodeIds;
|
||||
std::set<Id> edgeIds;
|
||||
|
||||
std::vector<Id> 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<StorageEdge> 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> graph = std::make_shared<Graph>();
|
||||
|
||||
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<Id> PersistentStorage::getActiveTokenIdsForId(Id tokenId, Id* declarationId) const
|
||||
{
|
||||
@@ -1506,7 +1585,8 @@ std::unordered_map<Id, std::set<Id>> PersistentStorage::getFileIdToImportingFile
|
||||
importedSourceLocationToElementIds[occurrence.sourceLocationId] = occurrence.elementId;
|
||||
}
|
||||
|
||||
for (const StorageSourceLocation& sourceLocation: m_sqliteIndexStorage.getAllByIds<StorageSourceLocation>(importedSourceLocationIds))
|
||||
for (const StorageSourceLocation& sourceLocation:
|
||||
m_sqliteIndexStorage.getAllByIds<StorageSourceLocation>(importedSourceLocationIds))
|
||||
{
|
||||
auto it = importedSourceLocationToElementIds.find(sourceLocation.id);
|
||||
if (it != importedSourceLocationToElementIds.end())
|
||||
@@ -1528,7 +1608,8 @@ std::unordered_map<Id, std::set<Id>> PersistentStorage::getFileIdToImportingFile
|
||||
return fileIdToImportingFileIdMap;
|
||||
}
|
||||
|
||||
std::set<Id> PersistentStorage::getReferenced(const std::set<Id>& ids, std::unordered_map<Id, std::set<Id>> idToReferencingIdMap) const
|
||||
std::set<Id> PersistentStorage::getReferenced(
|
||||
const std::set<Id>& ids, std::unordered_map<Id, std::set<Id>> idToReferencingIdMap) const
|
||||
{
|
||||
std::unordered_map<Id, std::set<Id>> idToReferencedIdMap;
|
||||
for (auto it: idToReferencingIdMap)
|
||||
@@ -1542,7 +1623,8 @@ std::set<Id> PersistentStorage::getReferenced(const std::set<Id>& ids, std::unor
|
||||
return getReferencing(ids, idToReferencedIdMap);
|
||||
}
|
||||
|
||||
std::set<Id> PersistentStorage::getReferencing(const std::set<Id>& ids, std::unordered_map<Id, std::set<Id>> idToReferencingIdMap) const
|
||||
std::set<Id> PersistentStorage::getReferencing(
|
||||
const std::set<Id>& ids, std::unordered_map<Id, std::set<Id>> idToReferencingIdMap) const
|
||||
{
|
||||
std::set<Id> referencingIds;
|
||||
|
||||
|
||||
@@ -117,6 +117,7 @@ public:
|
||||
|
||||
virtual std::shared_ptr<Graph> getGraphForAll() const;
|
||||
virtual std::shared_ptr<Graph> getGraphForActiveTokenIds(const std::vector<Id>& tokenIds, bool* isActiveNamespace = nullptr) const;
|
||||
virtual std::shared_ptr<Graph> getGraphForTrail(Id originId, Id targetId, Edge::EdgeTypeMask trailType, size_t depth) const;
|
||||
|
||||
virtual std::vector<Id> getActiveTokenIdsForId(Id tokenId, Id* declarationId) const;
|
||||
virtual std::vector<Id> getNodeIdsForLocationIds(const std::vector<Id>& locationIds) const;
|
||||
|
||||
@@ -52,6 +52,7 @@ public:
|
||||
|
||||
virtual std::shared_ptr<Graph> getGraphForAll() const = 0;
|
||||
virtual std::shared_ptr<Graph> getGraphForActiveTokenIds(const std::vector<Id>& tokenIds, bool* isActiveNamespace = nullptr) const = 0;
|
||||
virtual std::shared_ptr<Graph> getGraphForTrail(Id originId, Id targetId, Edge::EdgeTypeMask trailType, size_t depth) const = 0;
|
||||
|
||||
virtual std::vector<Id> getActiveTokenIdsForId(Id tokenId, Id* declarationId) const = 0;
|
||||
virtual std::vector<Id> getNodeIdsForLocationIds(const std::vector<Id>& locationIds) const = 0;
|
||||
|
||||
@@ -184,6 +184,16 @@ std::shared_ptr<Graph> StorageAccessProxy::getGraphForActiveTokenIds(const std::
|
||||
return std::make_shared<Graph>();
|
||||
}
|
||||
|
||||
std::shared_ptr<Graph> 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<Graph>();
|
||||
}
|
||||
|
||||
std::vector<Id> StorageAccessProxy::getActiveTokenIdsForId(Id tokenId, Id* delcarationId) const
|
||||
{
|
||||
if (hasSubject())
|
||||
|
||||
@@ -40,6 +40,7 @@ public:
|
||||
|
||||
virtual std::shared_ptr<Graph> getGraphForAll() const;
|
||||
virtual std::shared_ptr<Graph> getGraphForActiveTokenIds(const std::vector<Id>& tokenIds, bool* isActiveNamespace = nullptr) const;
|
||||
virtual std::shared_ptr<Graph> getGraphForTrail(Id originId, Id targetId, Edge::EdgeTypeMask trailType, size_t depth) const;
|
||||
|
||||
virtual std::vector<Id> getActiveTokenIdsForId(Id tokenId, Id* declarationId) const;
|
||||
virtual std::vector<Id> getNodeIdsForLocationIds(const std::vector<Id>& locationIds) const;
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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<Id, std::shared_ptr<Node>> m_nodes;
|
||||
std::map<Id, std::shared_ptr<Edge>> m_edges;
|
||||
|
||||
TrailMode m_trailMode;
|
||||
};
|
||||
|
||||
std::ostream& operator<<(std::ostream& ostream, const Graph& graph);
|
||||
|
||||
@@ -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<MessageActivateTrail>
|
||||
{
|
||||
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
|
||||
Reference in New Issue
Block a user