ui: redesigned graph overview and use new list layouting within overview bundles

* overview bundles imitate style of bundled node type
* removed bundle for built-in types
* show contents of overview bundle in vertical list layouted with new ListLayouter
* nodes starting with the same letter are grouped with the letter displayed above
* pressing the letter key will scroll to the group
* also use this list layouting for namespace & package contents
* renamed BucketGrid to BucketLayouter
* moved used functions of GraphPostProcessor to GraphViewStyle and removed GraphPostProcessor

fortune cookie message = Don't be afraid to take that big step
This commit is contained in:
Eberhard Graether
2017-02-02 13:29:40 +01:00
parent 993d66727d
commit 52b0cd2017
34 changed files with 577 additions and 578 deletions
@@ -378,6 +378,14 @@
<normal>#FFFFFF</normal>
</text>
</qualifier>
<text>
<fill>
<normal>transparent</normal>
</fill>
<text>
<normal>white</normal>
</text>
</text>
</node>
<edge>
<default>
+8
View File
@@ -349,6 +349,14 @@
<normal>black</normal>
</text>
</qualifier>
<text>
<fill>
<normal>transparent</normal>
</fill>
<text>
<normal>black</normal>
</text>
</text>
</node>
<edge>
<default>
+8
View File
@@ -352,6 +352,14 @@
<normal>#F7F7F7</normal>
</text>
</qualifier>
<text>
<fill>
<normal>transparent</normal>
</fill>
<text>
<normal>white</normal>
</text>
</text>
</node>
<edge>
<default>
+5 -5
View File
@@ -2,12 +2,12 @@
add_files(
LIB_FILES
component/controller/helper/BucketGrid.cpp
component/controller/helper/BucketGrid.h
component/controller/helper/BucketLayouter.cpp
component/controller/helper/BucketLayouter.h
component/controller/helper/DummyEdge.h
component/controller/helper/DummyNode.h
component/controller/helper/GraphPostprocessor.cpp
component/controller/helper/GraphPostprocessor.h
component/controller/helper/ListLayouter.cpp
component/controller/helper/ListLayouter.h
component/controller/helper/NetworkProtocolHelper.cpp
component/controller/helper/NetworkProtocolHelper.h
component/controller/helper/SnippetMerger.cpp
@@ -362,7 +362,7 @@ add_files(
utility/text/TextAccess.cpp
utility/text/TextAccess.h
utility/ApplicationArchitectureType.h
utility/AppPath.cpp
utility/AppPath.h
+111 -22
View File
@@ -7,7 +7,8 @@
#include "utility/utility.h"
#include "utility/utilityString.h"
#include "component/controller/helper/BucketGrid.h"
#include "component/controller/helper/BucketLayouter.h"
#include "component/controller/helper/ListLayouter.h"
#include "component/view/GraphView.h"
#include "component/view/GraphViewStyle.h"
#include "data/access/StorageAccess.h"
@@ -72,20 +73,30 @@ void GraphController::handleMessage(MessageActivateTokens* message)
std::vector<Id> tokenIds = utility::concat(m_activeNodeIds, m_activeEdgeIds);
std::shared_ptr<Graph> graph = m_storageAccess->getGraphForActiveTokenIds(tokenIds);
bool isNamespace = false;
std::shared_ptr<Graph> graph = m_storageAccess->getGraphForActiveTokenIds(tokenIds, &isNamespace);
createDummyGraphForTokenIdsAndSetActiveAndVisibility(tokenIds, graph);
if (m_activeNodeIds.size() == 1)
if (isNamespace)
{
bundleNodes();
addCharacterIndex();
layoutNesting();
layoutList();
}
else
{
if (m_activeNodeIds.size() == 1)
{
bundleNodes();
}
layoutNesting();
layoutGraph(true);
assignBundleIds();
}
layoutNesting();
layoutGraph(true);
assignBundleIds();
buildGraph(message, true);
buildGraph(message, !isNamespace, true, isNamespace);
}
void GraphController::handleMessage(MessageFlushUpdates* message)
@@ -115,8 +126,16 @@ void GraphController::handleMessage(MessageGraphNodeBundleSplit* message)
DummyNode* node = m_dummyNodes[i].get();
if (node->isBundleNode() && node->tokenId == message->bundleId)
{
m_dummyNodes.insert(m_dummyNodes.begin() + i + 1, node->bundledNodes.begin(), node->bundledNodes.end());
m_dummyNodes.erase(m_dummyNodes.begin() + i);
if (message->removeOtherNodes)
{
std::vector<std::shared_ptr<DummyNode>> nodes(node->bundledNodes.begin(), node->bundledNodes.end());
m_dummyNodes = nodes;
}
else
{
m_dummyNodes.insert(m_dummyNodes.begin() + i + 1, node->bundledNodes.begin(), node->bundledNodes.end());
m_dummyNodes.erase(m_dummyNodes.begin() + i);
}
break;
}
}
@@ -134,10 +153,19 @@ void GraphController::handleMessage(MessageGraphNodeBundleSplit* message)
std::vector<Id> tokenIds = utility::concat(m_activeNodeIds, m_activeEdgeIds);
setActiveAndVisibility(tokenIds);
layoutNesting();
layoutGraph();
if (message->layoutToList)
{
addCharacterIndex();
layoutNesting();
layoutList();
}
else
{
layoutNesting();
layoutGraph();
}
buildGraph(message, false);
buildGraph(message, false, true, message->layoutToList);
}
void GraphController::handleMessage(MessageGraphNodeExpand* message)
@@ -942,6 +970,7 @@ void GraphController::bundleByType(
if (bundleNode)
{
bundleNode->bundledNodeType = type;
m_dummyNodes.push_back(bundleNode);
}
}
@@ -959,9 +988,13 @@ void GraphController::bundleNodesByType()
nodes.push_back(oldNodes[i]);
}
bundleByType(nodes, Node::NODE_FILE, "Files");
bundleByType(nodes, Node::NODE_MACRO, "Macros");
bundleByType(nodes, Node::NODE_NAMESPACE, "Namespaces");
bundleByType(nodes, Node::NODE_PACKAGE, "Packages");
bundleByType(nodes, Node::NODE_BUILTIN_TYPE, "Built-in Types");
// bundleByType(nodes, Node::NODE_BUILTIN_TYPE, "Built-in Types");
bundleByType(nodes, Node::NODE_CLASS, "Classes");
bundleByType(nodes, Node::NODE_INTERFACE, "Interfaces");
bundleByType(nodes, Node::NODE_STRUCT, "Structs");
@@ -973,9 +1006,6 @@ void GraphController::bundleNodesByType()
bundleByType(nodes, Node::NODE_TYPEDEF, "Typedefs");
bundleByType(nodes, Node::NODE_ENUM, "Enums");
bundleByType(nodes, Node::NODE_FILE, "Files");
bundleByType(nodes, Node::NODE_MACRO, "Macros");
// // should never be visible
bundleByType(nodes, Node::NODE_METHOD, "Methods");
@@ -1022,6 +1052,7 @@ void GraphController::bundleNodesByType()
if (anonymousBundle)
{
anonymousBundle->bundledNodeType = Node::NODE_NAMESPACE;
bundleNode->bundledNodeCount = bundleNode->getBundledNodeCount() + anonymousBundle->getBundledNodeCount();
bundleNode->bundledNodes.insert(anonymousBundle);
}
@@ -1029,6 +1060,43 @@ void GraphController::bundleNodesByType()
}
}
void GraphController::addCharacterIndex()
{
// Remove index characters from last time
DummyNode::BundledNodesSet newNodes;
for (const std::shared_ptr<DummyNode> node : m_dummyNodes)
{
if (!node->isTextNode())
{
newNodes.insert(node);
}
}
m_dummyNodes.clear();
m_dummyNodes.insert(m_dummyNodes.end(), newNodes.begin(), newNodes.end());
// Add index characters
char character = 0;
for (size_t i = 0; i < m_dummyNodes.size(); i++)
{
if (!m_dummyNodes[i]->name.size())
{
continue;
}
if (toupper(m_dummyNodes[i]->name[0]) != character)
{
character = toupper(m_dummyNodes[i]->name[0]);
std::shared_ptr<DummyNode> textNode = std::make_shared<DummyNode>();
textNode->textNode = true;
textNode->name = character;
textNode->visible = true;
m_dummyNodes.insert(m_dummyNodes.begin() + i, textNode);
}
}
}
void GraphController::layoutNesting()
{
TRACE();
@@ -1067,12 +1135,23 @@ void GraphController::layoutNestingRecursive(DummyNode* node) const
}
else if (node->isBundleNode())
{
margins = GraphViewStyle::getMarginsOfBundleNode();
if (node->bundledNodeType != Node::NODE_UNDEFINED)
{
margins = GraphViewStyle::getMarginsForNodeType(node->bundledNodeType, false);
}
else
{
margins = GraphViewStyle::getMarginsOfBundleNode();
}
}
else if (node->isQualifierNode())
{
return;
}
else if (node->isTextNode())
{
margins = GraphViewStyle::getMarginsOfTextNode();
}
int y = 0;
int x = 0;
@@ -1277,7 +1356,7 @@ void GraphController::layoutGraph(bool getSortedNodes)
{
TRACE();
BucketGrid grid(getView()->getViewSize());
BucketLayouter grid(getView()->getViewSize());
grid.createBuckets(m_dummyNodes, m_dummyEdges);
grid.layoutBuckets();
@@ -1287,6 +1366,14 @@ void GraphController::layoutGraph(bool getSortedNodes)
}
}
void GraphController::layoutList()
{
TRACE();
ListLayouter layouter(getView()->getViewSize());
layouter.layoutList(m_dummyNodes);
}
void GraphController::assignBundleIds()
{
Id bundleId = 0;
@@ -1307,13 +1394,15 @@ DummyNode* GraphController::getDummyGraphNodeById(Id tokenId) const
return nullptr;
}
void GraphController::buildGraph(MessageBase* message, bool centerActiveNode, bool animatedTransition)
void GraphController::buildGraph(MessageBase* message, bool centerActiveNode, bool animatedTransition, bool scrollToTop)
{
if (!message->isReplayed())
{
GraphView::GraphParams params;
params.centerActiveNode = centerActiveNode;
params.animatedTransition = animatedTransition;
params.scrollToTop = scrollToTop;
params.isIndexedList = scrollToTop;
getView()->rebuildGraph(m_graph, m_dummyNodes, m_dummyEdges, params);
}
@@ -1591,7 +1680,7 @@ void GraphController::handleMessage(MessageColorSchemeTest* message)
node->layoutBucket.y = (i / 6) + 1;
}
BucketGrid grid(Vec2i(0, 0));
BucketLayouter grid(Vec2i(0, 0));
grid.createBuckets(m_dummyNodes, std::vector<std::shared_ptr<DummyEdge>>());
grid.layoutBuckets();
@@ -83,22 +83,28 @@ private:
void setNodeVisibilityRecursiveTopDown(DummyNode* node, bool parentExpanded) const;
void bundleNodes();
void bundleNodesAndEdgesMatching(std::function<bool(const DummyNode::BundleInfo&, const Node*)> matcher, size_t count, const std::string& name);
std::shared_ptr<DummyNode> bundleNodesMatching(std::list<std::shared_ptr<DummyNode>>& nodes, std::function<bool(const DummyNode*)> matcher, const std::string& name);
void bundleNodesAndEdgesMatching(
std::function<bool(const DummyNode::BundleInfo&, const Node*)> matcher, size_t count, const std::string& name);
std::shared_ptr<DummyNode> bundleNodesMatching(
std::list<std::shared_ptr<DummyNode>>& nodes, std::function<bool(const DummyNode*)> matcher, const std::string& name);
void bundleByType(std::list<std::shared_ptr<DummyNode>>& nodes, Node::NodeType type, const std::string& name);
void bundleNodesByType();
void addCharacterIndex();
void layoutNesting();
void layoutNestingRecursive(DummyNode* node) const;
void addExpandToggleNode(DummyNode* node) const;
void layoutToGrid(DummyNode* node) const;
void layoutGraph(bool getSortedNodes = false);
void layoutList();
void assignBundleIds();
DummyNode* getDummyGraphNodeById(Id tokenId) const;
void buildGraph(MessageBase* message, bool centerActiveNode, bool animatedTransition = true);
void buildGraph(MessageBase* message, bool centerActiveNode, bool animatedTransition = true, bool scrollToTop = false);
void forEachDummyNodeRecursive(std::function<void(DummyNode*)> func);
void forEachDummyEdge(std::function<void(DummyEdge*)> func);
@@ -1,4 +1,4 @@
#include "component/controller/helper/BucketGrid.h"
#include "component/controller/helper/BucketLayouter.h"
#include "component/controller/helper/DummyEdge.h"
#include "component/view/GraphViewStyle.h"
@@ -101,7 +101,7 @@ void Bucket::layout(int x, int y, int width, int height)
}
BucketGrid::BucketGrid(Vec2i viewSize)
BucketLayouter::BucketLayouter(Vec2i viewSize)
: m_viewSize(viewSize)
, m_i1(0)
, m_j1(0)
@@ -111,7 +111,7 @@ BucketGrid::BucketGrid(Vec2i viewSize)
m_buckets[0][0] = Bucket(0, 0);
}
void BucketGrid::createBuckets(
void BucketLayouter::createBuckets(
std::vector<std::shared_ptr<DummyNode>>& nodes, const std::vector<std::shared_ptr<DummyEdge>>& edges
){
if (!nodes.size())
@@ -194,7 +194,7 @@ void BucketGrid::createBuckets(
}
}
void BucketGrid::layoutBuckets()
void BucketLayouter::layoutBuckets()
{
std::map<int, int> widths;
std::map<int, int> heights;
@@ -245,7 +245,7 @@ void BucketGrid::layoutBuckets()
}
}
std::vector<std::shared_ptr<DummyNode>> BucketGrid::getSortedNodes()
std::vector<std::shared_ptr<DummyNode>> BucketLayouter::getSortedNodes()
{
std::vector<std::shared_ptr<DummyNode>> sortedNodes;
@@ -261,7 +261,7 @@ std::vector<std::shared_ptr<DummyNode>> BucketGrid::getSortedNodes()
return sortedNodes;
}
std::shared_ptr<DummyNode> BucketGrid::findTopMostDummyNodeRecursive(
std::shared_ptr<DummyNode> BucketLayouter::findTopMostDummyNodeRecursive(
std::vector<std::shared_ptr<DummyNode>>& nodes, Id tokenId, std::shared_ptr<DummyNode> top
){
for (std::shared_ptr<DummyNode> node : nodes)
@@ -283,13 +283,13 @@ std::shared_ptr<DummyNode> BucketGrid::findTopMostDummyNodeRecursive(
return nullptr;
}
void BucketGrid::addNode(std::shared_ptr<DummyNode> node)
void BucketLayouter::addNode(std::shared_ptr<DummyNode> node)
{
Bucket* bucket = getBucket(node->layoutBucket.x, node->layoutBucket.y);
bucket->addNode(node);
}
bool BucketGrid::addNode(std::shared_ptr<DummyNode> owner, std::shared_ptr<DummyNode> target, bool horizontal)
bool BucketLayouter::addNode(std::shared_ptr<DummyNode> owner, std::shared_ptr<DummyNode> target, bool horizontal)
{
Bucket* ownerBucket = getBucket(owner);
Bucket* targetBucket = getBucket(target);
@@ -324,7 +324,7 @@ bool BucketGrid::addNode(std::shared_ptr<DummyNode> owner, std::shared_ptr<Dummy
return true;
}
Bucket* BucketGrid::getBucket(int i, int j)
Bucket* BucketLayouter::getBucket(int i, int j)
{
bool newColumn = false;
bool newRow = false;
@@ -375,7 +375,7 @@ Bucket* BucketGrid::getBucket(int i, int j)
return nullptr;
}
Bucket* BucketGrid::getBucket(std::shared_ptr<DummyNode> node)
Bucket* BucketLayouter::getBucket(std::shared_ptr<DummyNode> node)
{
for (int j = m_j1; j <= m_j2; j++)
{
@@ -1,5 +1,5 @@
#ifndef BUCKET_GRID_H
#define BUCKET_GRID_H
#ifndef BUCKET_LAYOUTER_H
#define BUCKET_LAYOUTER_H
#include <map>
@@ -38,10 +38,10 @@ private:
};
class BucketGrid
class BucketLayouter
{
public:
BucketGrid(Vec2i viewSize);
BucketLayouter(Vec2i viewSize);
void createBuckets(
std::vector<std::shared_ptr<DummyNode>>& nodes,
const std::vector<std::shared_ptr<DummyEdge>>& edges);
@@ -68,4 +68,4 @@ private:
int m_j2;
};
#endif // BUCKET_GRID_H
#endif // BUCKET_LAYOUTER_H
@@ -6,11 +6,10 @@
#include "utility/utility.h"
#include "utility/utilityString.h"
#include "data/graph/Node.h"
#include "data/graph/token_component/TokenComponentAccess.h"
#include "data/name/NameHierarchy.h"
class Node;
// temporary data structure for (visual) graph creation process
struct DummyNode
{
@@ -68,6 +67,8 @@ public:
, bundleId(0)
, layoutBucket(0, 0)
, bundledNodeCount(0)
, bundledNodeType(Node::NODE_UNDEFINED)
, textNode(false)
{
}
@@ -83,7 +84,7 @@ public:
bool isExpandToggleNode() const
{
return !isGraphNode() && !isAccessNode() && !isBundleNode() && !isQualifierNode();
return !isGraphNode() && !isAccessNode() && !isBundleNode() && !isQualifierNode() && !isTextNode();
}
bool isBundleNode() const
@@ -96,6 +97,11 @@ public:
return qualifierName.size();
}
bool isTextNode() const
{
return textNode;
}
bool isExpanded() const
{
return expanded;
@@ -269,9 +275,13 @@ public:
// BundleNode
BundledNodesSet bundledNodes;
size_t bundledNodeCount;
Node::NodeType bundledNodeType;
// QualifierNode
NameHierarchy qualifierName;
// TextNode
bool textNode;
};
#endif // DUMMY_NODE_H
@@ -1,467 +0,0 @@
#include "component/controller/helper/GraphPostprocessor.h"
#include "component/view/GraphViewStyle.h"
unsigned int GraphPostprocessor::s_cellWidth = GraphViewStyle::s_gridCellSize;
unsigned int GraphPostprocessor::s_cellHeight = GraphViewStyle::s_gridCellSize;
unsigned int GraphPostprocessor::s_cellPadding = GraphViewStyle::s_gridCellPadding;
void GraphPostprocessor::doPostprocessing(std::vector<DummyNode>& nodes)
{
unsigned int atomarGridWidth = s_cellWidth;
unsigned int atomarGridHeight = s_cellHeight;
if (nodes.size() < 2)
{
LOG_INFO_STREAM(<< "Skipping postprocessing, need at least 2 nodes but got " << nodes.size());
return;
}
// determine center of mass (CoD) which is used to get outliers closer to the rest of the graph
int divisorWidth = 999999;
int divisorHeight = 999999;
int maxNodeWidth = 0;
int maxNodeHeight = 0;
Vec2i centerOfMass(0, 0);
float totalMass = 0.0f;
for (const DummyNode& node : nodes)
{
if (node.size.x < divisorWidth)
{
divisorWidth = node.size.x;
}
if (node.size.y < divisorHeight)
{
divisorHeight = node.size.y;
}
if (node.size.x > maxNodeWidth)
{
maxNodeWidth = node.size.x;
}
else if (node.size.y > maxNodeHeight)
{
maxNodeHeight = node.size.y;
}
float nodeMass = node.size.x * node.size.y;
centerOfMass += node.position * nodeMass;
totalMass += nodeMass;
}
centerOfMass /= totalMass;
divisorWidth = (int)atomarGridWidth + s_cellPadding; //std::min(divisor, (int)atomarGridSize);
divisorHeight = (int)atomarGridHeight + s_cellPadding;
resolveOutliers(nodes, centerOfMass);
// the nodes will be aligned everytime they move during post-processing
// align all nodes once here so that nodes that won't be moved again are aligned
for (DummyNode& node : nodes)
{
alignNodeOnRaster(node);
}
MatrixDynamicBase<unsigned int> heatMap = buildHeatMap(nodes, divisorWidth, divisorHeight, maxNodeWidth, maxNodeHeight);
resolveOverlap(nodes, heatMap, divisorWidth, divisorHeight);
}
void GraphPostprocessor::alignNodeOnRaster(DummyNode& node)
{
node.position = alignOnRaster(node.position);
}
Vec2i GraphPostprocessor::alignOnRaster(Vec2i position)
{
int rasterPosDivisor = s_cellWidth + s_cellPadding;
if (position.x % rasterPosDivisor != 0)
{
int t = position.x / rasterPosDivisor;
int r = position.x % rasterPosDivisor;
if (std::abs(r) > rasterPosDivisor/2)
{
if (t != 0)
{
t += (t / std::abs(t));
}
else if (r != 0)
{
t += (r / std::abs(r));
}
}
position.x = t * rasterPosDivisor;
}
if (position.y % rasterPosDivisor != 0)
{
int t = position.y / rasterPosDivisor;
int r = position.y % rasterPosDivisor;
if (std::abs(r) > rasterPosDivisor/2)
{
if(t != 0)
{
t += (t / std::abs(t));
}
else if(r != 0)
{
t += (r / std::abs(r));
}
}
position.y = t * rasterPosDivisor;
}
return position;
}
void GraphPostprocessor::resolveOutliers(std::vector<DummyNode>& nodes, const Vec2i& centerPoint)
{
float maxDist = 0.0f;
for (const DummyNode& node : nodes)
{
Vec2i toCenterOfMass = centerPoint - node.position;
if (toCenterOfMass.getLength() > maxDist)
{
maxDist = toCenterOfMass.getLength();
}
}
if (maxDist == 0.0f)
{
return;
}
for (DummyNode& node : nodes)
{
Vec2i toCenterOfMass = centerPoint - node.position;
float dist = toCenterOfMass.getLength();
// causes far away nodes to be effected stronger than nodes that are already close to the center
float distFactor = std::sqrt(dist / maxDist);
node.position += toCenterOfMass * distFactor;
}
}
MatrixDynamicBase<unsigned int> GraphPostprocessor::buildHeatMap(
const std::vector<DummyNode>& nodes, const int atomarNodeWidth, const int atomarNodeHeight, const int maxNodeWidth, const int maxNodeHeight)
{
int heatMapWidth = (maxNodeWidth * nodes.size() / atomarNodeWidth) * 5; // theoretically the nodes could horizontally or vertically far from the center, therefore '*5' (it's kinda arbitrary, generally *2 should suffice, I use *5 to prevent problems in extrem cases)
int heatMapHeight = (maxNodeHeight * nodes.size() / atomarNodeHeight) * 5;
MatrixDynamicBase<unsigned int> heatMap(heatMapWidth, heatMapHeight);
for (const DummyNode& node : nodes)
{
int left = node.position.x / atomarNodeWidth + heatMapWidth/2;
int up = node.position.y / atomarNodeHeight + heatMapHeight/2;
Vec2i size = calculateRasterNodeSize(node);
int width = size.x;
int height = size.y;
if (left + width > heatMapWidth || left < 0)
{
continue;
}
if (up + height > heatMapHeight || up < 0)
{
continue;
}
for (int i = 0; i < width; i++)
{
for (int j = 0; j < height; j++)
{
unsigned int x = left + i;
unsigned int y = up + j;
unsigned int value = heatMap.getValue(x, y);
heatMap.setValue(x, y, value+1);
}
}
}
return heatMap;
}
void GraphPostprocessor::resolveOverlap(
std::vector<DummyNode>& nodes, MatrixDynamicBase<unsigned int>& heatMap, const int divisorWidth, const int divisorHeight)
{
int heatMapWidth = heatMap.getColumnsCount();
int heatMapHeight = heatMap.getRowsCount();
bool overlap = true;
int iterationCount = 0;
int maxIterations = 15;
while (overlap && iterationCount < maxIterations)
{
LOG_INFO_STREAM(<< iterationCount);
overlap = false;
iterationCount++;
for (DummyNode& node : nodes)
{
Vec2i nodePos(0, 0);
nodePos.x = node.position.x / divisorWidth + heatMapWidth/2;
nodePos.y = node.position.y / divisorHeight + heatMapHeight/2;
Vec2i nodeSize = calculateRasterNodeSize(node);
if (nodePos.x + nodeSize.x > heatMapWidth || nodePos.x < 0)
{
LOG_WARNING("Leaving heatmap area in x");
continue;
}
if (nodePos.y + nodeSize.y > heatMapHeight || nodePos.y < 0)
{
LOG_WARNING("Leaving heatmap area in y");
continue;
}
Vec2f grad(0.0f, 0.0f);
if (getHeatmapGradient(grad, heatMap, nodePos, nodeSize))
{
overlap = true;
}
// handle overlap with no gradient
// e.g. when a node lies completely on top of another
if (grad.getLengthSquared() <= 0.000001f && overlap)
{
grad = node.position;
grad.normalize();
// catch special case of node being at position 0/0
if (grad.getLengthSquared() <= 0.000001f)
{
grad.y = 1.0f;
}
grad *= -1.0f;
}
// remove node temporarily from heat map, it will be re-added at the new position later on
modifyHeatmapArea(heatMap, nodePos, nodeSize, -1);
// move node to new position
int xOffset = grad.x * divisorWidth;
int yOffset = grad.y * divisorHeight;
int maxXOffset = 2*divisorWidth;
int maxYOffset = 2*divisorHeight;
// prevent the graph from "exploding" again...
if (xOffset > maxXOffset)
{
xOffset = maxXOffset;
}
else if (xOffset < -maxXOffset)
{
xOffset = -maxXOffset;
}
if (yOffset > maxYOffset)
{
yOffset = maxYOffset;
}
else if (yOffset < -maxYOffset)
{
yOffset = -maxYOffset;
}
node.position += Vec2i(xOffset, yOffset);
alignNodeOnRaster(node);
// re-add node to heat map at new position
nodePos.x = node.position.x / divisorWidth + heatMapWidth/2;
nodePos.y = node.position.y / divisorHeight + heatMapHeight/2;
modifyHeatmapArea(heatMap, nodePos, nodeSize, 1);
if (getHeatmapGradient(grad, heatMap, nodePos, nodeSize))
{
overlap = true;
}
}
}
}
void GraphPostprocessor::modifyHeatmapArea(
MatrixDynamicBase<unsigned int>& heatMap, const Vec2i& leftUpperCorner, const Vec2i& size, const int modifier
){
bool wentOutOfRange = false;
for (int i = 0; i < size.x; i++)
{
for (int j = 0; j < size.y; j++)
{
int x = leftUpperCorner.x + i;
int y = leftUpperCorner.y + j;
if (x < 0 || x > static_cast<int>(heatMap.getColumnsCount()-1))
{
wentOutOfRange = true;
continue;
}
if (y < 0 || y > static_cast<int>(heatMap.getRowsCount()-1))
{
wentOutOfRange = true;
continue;
}
unsigned int value = heatMap.getValue(x, y);
heatMap.setValue(x, y, value+modifier);
if (wentOutOfRange == true)
{
LOG_WARNING("Left matrix range while trying to modify values.");
}
}
}
}
bool GraphPostprocessor::getHeatmapGradient(
Vec2f& outGradient, const MatrixDynamicBase<unsigned int>& heatMap, const Vec2i& leftUpperCorner, const Vec2i& size
){
bool overlap = false;
for (int i = 0; i < size.x; i++)
{
for (int j = 0; j < size.y; j++)
{
int x = leftUpperCorner.x + i;
int y = leftUpperCorner.y + j;
// weight factors that emphasize gradients near the nodes center
int hMagFactor = std::max(1, (int)(size.x * 0.5 - std::abs(i + 1 - size.x * 0.5)));
int vMagFactor = std::max(1, (int)(size.y * 0.5 - std::abs(j + 1 - size.y * 0.5)));
// if x and y lie directly at the border not all 4 neighbours can be checked
if (x < 1 || x > static_cast<int>(heatMap.getColumnsCount() - 2))
{
continue;
}
if (y < 1 || y > static_cast<int>(heatMap.getRowsCount() - 2))
{
continue;
}
float val = heatMap.getValue(x, y);
float xP1 = heatMap.getValue(x + 1, y) * hMagFactor;
float xM1 = heatMap.getValue(x - 1, y) * hMagFactor;
float yP1 = heatMap.getValue(x, y + 1) * vMagFactor;
float yM1 = heatMap.getValue(x, y - 1) * vMagFactor;
xP1 = std::sqrt(xP1);
xM1 = std::sqrt(xM1);
yP1 = std::sqrt(yP1);
yM1 = std::sqrt(yM1);
float xOffset = (xM1 - val) + (val - xP1);
float yOffset = (yM1 - val) + (val - yP1);
outGradient += Vec2f(xOffset, yOffset);
if (val > 1)
{
overlap = true;
}
}
}
return overlap;
}
Vec2f GraphPostprocessor::heatMapRayCast(
const MatrixDynamicBase<unsigned int>& heatMap, const Vec2f& startPosition, const Vec2f& direction, unsigned int minValue
){
float xOffset = 0.0f;
float yOffset = 0.0f;
if (std::abs(direction.x) > 0.0000000001f)
{
xOffset = direction.x / std::abs(direction.x);
}
if (std::abs(direction.y) > 0.0000000001f)
{
yOffset = direction.y / std::abs(direction.y);
}
if (startPosition.x < 1 || startPosition.x > static_cast<int>(heatMap.getColumnsCount() - 2))
{
return Vec2f(0.0f, 0.0f);
}
if (startPosition.y < 1 || startPosition.y > static_cast<int>(heatMap.getRowsCount() - 2))
{
return Vec2f(0.0f, 0.0f);
}
Vec2f length(0.0f, 0.0f);
bool hit = false;
float posX = startPosition.x + xOffset;
float posY = startPosition.y + yOffset;
do
{
if (heatMap.getValue(posX, posY) >= minValue)
{
hit = true;
length.x = length.x + xOffset;
length.y = length.y + yOffset;
posX += xOffset;
posY += yOffset;
}
else
{
hit = false;
}
}
while (hit);
return length;
}
Vec2i GraphPostprocessor::calculateRasterNodeSize(const DummyNode& node)
{
Vec2i size = node.size;
Vec2i rasterSize(0, 0);
while (size.x > 0)
{
size.x = size.x - s_cellWidth;
if(size.x > 0)
{
size.x = size.x - s_cellPadding;
}
rasterSize.x = rasterSize.x + 1;
}
while (size.y > 0)
{
size.y = size.y - s_cellHeight;
if(size.y > 0)
{
size.y = size.y - s_cellPadding;
}
rasterSize.y = rasterSize.y + 1;
}
return rasterSize;
}
@@ -1,34 +0,0 @@
#ifndef GRAPH_POSTPROCESSOR_H
#define GRAPH_POSTPROCESSOR_H
#include <memory>
#include <list>
#include "utility/math/MatrixDynamicBase.h"
#include "component/controller/helper/DummyNode.h"
class GraphPostprocessor
{
public:
static void doPostprocessing(std::vector<DummyNode>& nodes);
static void alignNodeOnRaster(DummyNode& node);
static Vec2i alignOnRaster(Vec2i position);
private:
static unsigned int s_cellWidth;
static unsigned int s_cellHeight;
static unsigned int s_cellPadding;
static MatrixDynamicBase<unsigned int> buildHeatMap(const std::vector<DummyNode>& nodes, const int atomarNodeWidth, const int atomarNodeHeight, const int maxNodeWidth, const int maxNodeHeight);
static void resolveOutliers(std::vector<DummyNode>& nodes, const Vec2i& centerPoint);
static void resolveOverlap(std::vector<DummyNode>& nodes, MatrixDynamicBase<unsigned int>& heatMap, const int divisorWidth, const int divisorHeight);
static void modifyHeatmapArea(MatrixDynamicBase<unsigned int>& heatMap, const Vec2i& leftUpperCorner, const Vec2i& size, const int modifier);
static bool getHeatmapGradient(Vec2f& outGradient, const MatrixDynamicBase<unsigned int>& heatMap, const Vec2i& leftUpperCorner, const Vec2i& size);
static Vec2f heatMapRayCast(const MatrixDynamicBase<unsigned int>& heatMap, const Vec2f& startPosition, const Vec2f& direction, unsigned int minValue);
static Vec2i calculateRasterNodeSize(const DummyNode& node);
};
#endif // GRAPH_POSTPROCESSOR_H
@@ -0,0 +1,110 @@
#include "component/controller/helper/ListLayouter.h"
#include <algorithm>
#include "component/controller/helper/DummyNode.h"
#include "component/view/GraphViewStyle.h"
ListLayouter::ListLayouter(Vec2i viewSize)
: m_viewSize(viewSize)
{
}
void ListLayouter::layoutList(std::vector<std::shared_ptr<DummyNode>>& nodes)
{
size_t colsFinal;
std::vector<int> maxWidthsFinal;
int gapX = GraphViewStyle::s_gridCellSize + 2 * GraphViewStyle::s_gridCellPadding;
int gapY = GraphViewStyle::s_gridCellPadding;
for (size_t cols = 1; cols <= 10; cols++)
{
std::vector<int> maxWidths = std::vector<int>(cols, 0);
size_t nodesPerCol = cols == 1 ? nodes.size() : std::ceil((nodes.size() + cols - 1) / double(cols));
int maxHeight = 0;
int height = -gapY;
for (size_t i = 0; i < nodes.size(); i++)
{
size_t j = i / nodesPerCol;
if (i % nodesPerCol == 0)
{
height = -gapY;
}
height += nodes[i]->size.y() + gapY;
maxWidths[j] = std::max(nodes[i]->size.x(), maxWidths[j]);
maxHeight = std::max(height, maxHeight);
}
int width = -gapX;
for (size_t j = 0; j < cols; j++)
{
width += maxWidths[j] + gapX;
}
if (width > m_viewSize.x)
{
if (!maxWidthsFinal.size())
{
colsFinal = 1;
maxWidthsFinal = maxWidths;
}
break;
}
colsFinal = cols;
maxWidthsFinal = maxWidths;
if (height < m_viewSize.y)
{
break;
}
}
int x = 0;
int y = 0;
size_t nodesPerCol = colsFinal == 1 ? nodes.size() : std::ceil((nodes.size() + colsFinal - 1) / double(colsFinal));
std::shared_ptr<DummyNode> lastTextNode;
for (size_t i = 0; i < nodes.size(); i++)
{
size_t j = i / nodesPerCol;
if (j != 0 && j != colsFinal && i % nodesPerCol == 0)
{
if (lastTextNode)
{
std::shared_ptr<DummyNode> textNode = std::make_shared<DummyNode>(*lastTextNode.get());
if (nodes[i - 1] == lastTextNode)
{
lastTextNode->visible = false;
}
else if (textNode->name.size() == 1)
{
textNode->name += "..";
}
nodes.insert(nodes.begin() + i, textNode);
lastTextNode.reset();
i--;
continue;
}
y = 0;
x += maxWidthsFinal[j - 1] + gapX;
}
nodes[i]->position.x = x;
nodes[i]->position.y = y;
y += nodes[i]->size.y + gapY;
if (nodes[i]->isTextNode())
{
lastTextNode = nodes[i];
}
}
}
@@ -0,0 +1,19 @@
#ifndef LIST_LAYOUTER_H
#define LIST_LAYOUTER_H
#include "utility/math/Vector2.h"
struct DummyNode;
class ListLayouter
{
public:
ListLayouter(Vec2i viewSize);
void layoutList(std::vector<std::shared_ptr<DummyNode>>& nodes);
private:
Vec2i m_viewSize;
};
#endif // LIST_LAYOUTER_H
+2
View File
@@ -20,6 +20,8 @@ public:
{
bool animatedTransition;
bool centerActiveNode;
bool scrollToTop;
bool isIndexedList;
};
GraphView(ViewLayout* viewLayout);
+83
View File
@@ -23,6 +23,53 @@ float GraphViewStyle::s_zoomFactor;
std::map<std::string, GraphViewStyle::NodeColor> GraphViewStyle::s_nodeColors;
std::map<std::string, std::string> GraphViewStyle::s_edgeColors;
Vec2i GraphViewStyle::alignOnRaster(Vec2i position)
{
int rasterPosDivisor = s_gridCellSize + s_gridCellPadding;
if (position.x % rasterPosDivisor != 0)
{
int t = position.x / rasterPosDivisor;
int r = position.x % rasterPosDivisor;
if (std::abs(r) > rasterPosDivisor/2)
{
if (t != 0)
{
t += (t / std::abs(t));
}
else if (r != 0)
{
t += (r / std::abs(r));
}
}
position.x = t * rasterPosDivisor;
}
if (position.y % rasterPosDivisor != 0)
{
int t = position.y / rasterPosDivisor;
int r = position.y % rasterPosDivisor;
if (std::abs(r) > rasterPosDivisor/2)
{
if(t != 0)
{
t += (t / std::abs(t));
}
else if(r != 0)
{
t += (r / std::abs(r));
}
}
position.y = t * rasterPosDivisor;
}
return position;
}
GraphViewStyle::NodeMargins::NodeMargins()
: left(0)
, right(0)
@@ -176,6 +223,11 @@ size_t GraphViewStyle::getFontSizeOfQualifier()
return s_fontSize - 3;
}
size_t GraphViewStyle::getFontSizeOfTextNode()
{
return s_fontSize + 5;
}
std::string GraphViewStyle::getFontNameForNodeType(Node::NodeType type)
{
return s_fontName;
@@ -191,6 +243,11 @@ std::string GraphViewStyle::getFontNameOfExpandToggleNode()
return "Fira Sans";
}
std::string GraphViewStyle::getFontNameOfTextNode()
{
return "Fira Sans";
}
GraphViewStyle::NodeMargins GraphViewStyle::getMarginsForNodeType(Node::NodeType type, bool hasChildren)
{
NodeMargins margins;
@@ -308,6 +365,17 @@ GraphViewStyle::NodeMargins GraphViewStyle::getMarginsOfBundleNode()
return getMarginsForNodeType(Node::NODE_ENUM, false);
}
GraphViewStyle::NodeMargins GraphViewStyle::getMarginsOfTextNode()
{
NodeMargins margins;
margins.left = margins.right = 0;
margins.top = margins.bottom = 6;
margins.minWidth = margins.charHeight = getFontSizeOfTextNode();
return margins;
}
GraphViewStyle::NodeStyle GraphViewStyle::getStyleForNodeType(
Node::NodeType type, bool defined, bool isActive, bool isFocused, bool hasChildren, bool hasQualifier
){
@@ -463,6 +531,21 @@ GraphViewStyle::NodeStyle GraphViewStyle::getStyleOfQualifier()
return style;
}
GraphViewStyle::NodeStyle GraphViewStyle::getStyleOfTextNode()
{
NodeStyle style;
style.color = getNodeColor("text", false);
style.fontName = getFontNameOfTextNode();
style.fontSize = getFontSizeOfTextNode();
style.fontBold = true;
style.textOffset.y = 10;
return style;
}
GraphViewStyle::EdgeStyle GraphViewStyle::getStyleForEdgeType(Edge::EdgeType type, bool isActive, bool isFocused)
{
EdgeStyle style;
+6
View File
@@ -14,6 +14,8 @@ class GraphViewStyleImpl;
class GraphViewStyle
{
public:
static Vec2i alignOnRaster(Vec2i position);
struct NodeMargins
{
NodeMargins();
@@ -104,15 +106,18 @@ public:
static size_t getFontSizeOfExpandToggleNode();
static size_t getFontSizeOfCountCircle();
static size_t getFontSizeOfQualifier();
static size_t getFontSizeOfTextNode();
static std::string getFontNameForNodeType(Node::NodeType type);
static std::string getFontNameOfAccessNode();
static std::string getFontNameOfExpandToggleNode();
static std::string getFontNameOfTextNode();
static NodeMargins getMarginsForNodeType(Node::NodeType type, bool hasChildren);
static NodeMargins getMarginsOfAccessNode(AccessKind access);
static NodeMargins getMarginsOfExpandToggleNode();
static NodeMargins getMarginsOfBundleNode();
static NodeMargins getMarginsOfTextNode();
static NodeStyle getStyleForNodeType(
Node::NodeType type, bool defined, bool isActive, bool isFocused, bool hasChildren, bool hasQualifier);
@@ -121,6 +126,7 @@ public:
static NodeStyle getStyleOfCountCircle();
static NodeStyle getStyleOfBundleNode(bool isFocused);
static NodeStyle getStyleOfQualifier();
static NodeStyle getStyleOfTextNode();
static EdgeStyle getStyleForEdgeType(Edge::EdgeType type, bool isActive, bool isFocused);
+6 -1
View File
@@ -756,7 +756,7 @@ std::shared_ptr<Graph> PersistentStorage::getGraphForAll() const
return graph;
}
std::shared_ptr<Graph> PersistentStorage::getGraphForActiveTokenIds(const std::vector<Id>& tokenIds) const
std::shared_ptr<Graph> PersistentStorage::getGraphForActiveTokenIds(const std::vector<Id>& tokenIds, bool* isActiveNamespace) const
{
TRACE();
@@ -863,6 +863,11 @@ std::shared_ptr<Graph> PersistentStorage::getGraphForActiveTokenIds(const std::v
addComponentAccessToGraph(graph);
if (isActiveNamespace)
{
*isActiveNamespace = isNamespace;
}
return g;
}
+1 -1
View File
@@ -93,7 +93,7 @@ public:
virtual std::vector<SearchMatch> getSearchMatchesForTokenIds(const std::vector<Id>& elementIds) const;
virtual std::shared_ptr<Graph> getGraphForAll() const;
virtual std::shared_ptr<Graph> getGraphForActiveTokenIds(const std::vector<Id>& tokenIds) const;
virtual std::shared_ptr<Graph> getGraphForActiveTokenIds(const std::vector<Id>& tokenIds, bool* isActiveNamespace = nullptr) const;
virtual std::vector<Id> getActiveTokenIdsForId(Id tokenId, Id* declarationId) const;
+1 -1
View File
@@ -42,7 +42,7 @@ public:
virtual std::vector<SearchMatch> getSearchMatchesForTokenIds(const std::vector<Id>& tokenIds) const = 0;
virtual std::shared_ptr<Graph> getGraphForAll() const = 0;
virtual std::shared_ptr<Graph> getGraphForActiveTokenIds(const std::vector<Id>& tokenIds) const = 0;
virtual std::shared_ptr<Graph> getGraphForActiveTokenIds(const std::vector<Id>& tokenIds, bool* isActiveNamespace = nullptr) const = 0;
virtual std::vector<Id> getActiveTokenIdsForId(Id tokenId, Id* declarationId) const = 0;
+2 -2
View File
@@ -117,11 +117,11 @@ std::shared_ptr<Graph> StorageAccessProxy::getGraphForAll() const
return std::make_shared<Graph>();
}
std::shared_ptr<Graph> StorageAccessProxy::getGraphForActiveTokenIds(const std::vector<Id>& tokenIds) const
std::shared_ptr<Graph> StorageAccessProxy::getGraphForActiveTokenIds(const std::vector<Id>& tokenIds, bool* isActiveNamespace) const
{
if (hasSubject())
{
return m_subject->getGraphForActiveTokenIds(tokenIds);
return m_subject->getGraphForActiveTokenIds(tokenIds, isActiveNamespace);
}
return std::make_shared<Graph>();
+1 -1
View File
@@ -31,7 +31,7 @@ public:
virtual std::vector<SearchMatch> getSearchMatchesForTokenIds(const std::vector<Id>& tokenIds) const;
virtual std::shared_ptr<Graph> getGraphForAll() const;
virtual std::shared_ptr<Graph> getGraphForActiveTokenIds(const std::vector<Id>& tokenIds) const;
virtual std::shared_ptr<Graph> getGraphForActiveTokenIds(const std::vector<Id>& tokenIds, bool* isActiveNamespace = nullptr) const;
virtual std::vector<Id> getActiveTokenIdsForId(Id tokenId, Id* declarationId) const;
+7
View File
@@ -12,6 +12,7 @@ public:
Property<T>& operator=(const Property<T>& property);
operator const T&() const;
T& operator()() const;
private:
T* m_valuePointer;
@@ -47,4 +48,10 @@ Property<T>::operator const T&() const
return *m_valuePointer;
}
template<class T>
T& Property<T>::operator()() const
{
return *m_valuePointer;
}
#endif // PROPERTY_H
@@ -8,8 +8,10 @@ class MessageGraphNodeBundleSplit
: public Message<MessageGraphNodeBundleSplit>
{
public:
MessageGraphNodeBundleSplit(Id bundleId)
MessageGraphNodeBundleSplit(Id bundleId, bool removeOtherNodes = false, bool layoutToList = false)
: bundleId(bundleId)
, removeOtherNodes(removeOtherNodes)
, layoutToList(layoutToList)
{
}
@@ -24,6 +26,8 @@ public:
}
Id bundleId;
bool removeOtherNodes;
bool layoutToList;
};
#endif // MESSAGE_GRAPH_NODE_BUNDLE_SPLIT_H
+2
View File
@@ -98,6 +98,8 @@ add_files(
qt/view/graphElements/QtGraphNodeExpandToggle.h
qt/view/graphElements/QtGraphNodeQualifier.cpp
qt/view/graphElements/QtGraphNodeQualifier.h
qt/view/graphElements/QtGraphNodeText.cpp
qt/view/graphElements/QtGraphNodeText.h
qt/view/QtCodeView.cpp
qt/view/QtCodeView.h
+7 -1
View File
@@ -66,7 +66,7 @@ QtGraphicsView::QtGraphicsView(QWidget* parent)
float QtGraphicsView::getZoomFactor() const
{
return m_appZoomFactor;
return m_appZoomFactor * m_zoomFactor;
}
void QtGraphicsView::setAppZoomFactor(float appZoomFactor)
@@ -181,6 +181,12 @@ void QtGraphicsView::mouseReleaseEvent(QMouseEvent *event)
void QtGraphicsView::keyPressEvent(QKeyEvent* event)
{
if (event->key() >= Qt::Key_A && event->key() <= Qt::Key_Z)
{
QChar c = event->text().at(0).toUpper();
emit characterKeyPressed(c);
}
bool moved = moves();
switch (event->key())
+1
View File
@@ -45,6 +45,7 @@ protected:
signals:
void emptySpaceClicked();
void characterKeyPressed(QChar c);
private slots:
void updateTimer();
+77 -11
View File
@@ -11,9 +11,7 @@
#include "component/controller/helper/DummyEdge.h"
#include "component/controller/helper/DummyNode.h"
#include "component/controller/helper/GraphPostprocessor.h"
#include "component/view/GraphViewStyle.h"
#include "settings/ColorScheme.h"
#include "settings/ApplicationSettings.h"
#include "utility/messaging/type/MessageDeactivateEdge.h"
#include "utility/ResourcePaths.h"
@@ -29,6 +27,7 @@
#include "qt/view/graphElements/QtGraphNodeData.h"
#include "qt/view/graphElements/QtGraphNodeExpandToggle.h"
#include "qt/view/graphElements/QtGraphNodeQualifier.h"
#include "qt/view/graphElements/QtGraphNodeText.h"
QtGraphView::QtGraphView(ViewLayout* viewLayout)
: GraphView(viewLayout)
@@ -40,6 +39,8 @@ 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_scrollToTop(false)
, m_isIndexedList(false)
{
}
@@ -71,6 +72,7 @@ void QtGraphView::initView()
widget->layout()->addWidget(view);
connect(view, SIGNAL(emptySpaceClicked()), this, SLOT(clickedInEmptySpace()));
connect(view, SIGNAL(characterKeyPressed(QChar)), this, SLOT(pressedCharacterKey(QChar)));
m_scrollSpeedChangeListenerHorizontal.setScrollBar(view->horizontalScrollBar());
m_scrollSpeedChangeListenerVertical.setScrollBar(view->verticalScrollBar());
@@ -109,18 +111,25 @@ Vec2i QtGraphView::getViewSize() const
QtGraphicsView* view = getView();
float zoomFactor = view->getZoomFactor();
return Vec2i(view->width() / zoomFactor - 80, view->height() / zoomFactor - 80);
return Vec2i(view->width() / zoomFactor - 60, view->height() / zoomFactor - 60);
}
void QtGraphView::centerScrollBars()
void QtGraphView::updateScrollBars()
{
QGraphicsView* view = getView();
QScrollBar* hb = view->horizontalScrollBar();
QScrollBar* vb = view->verticalScrollBar();
hb->setValue((hb->minimum() + hb->maximum()) / 2);
vb->setValue((vb->minimum() + vb->maximum()) / 2);
if (m_scrollToTop)
{
vb->setValue(vb->minimum());
}
else
{
hb->setValue((hb->minimum() + hb->maximum()) / 2);
vb->setValue((vb->minimum() + vb->maximum()) / 2);
}
}
void QtGraphView::finishedTransition()
@@ -148,6 +157,50 @@ void QtGraphView::clickedInEmptySpace()
}
}
void QtGraphView::pressedCharacterKey(QChar c)
{
if (!m_isIndexedList)
{
return;
}
QtGraphNode* node = nullptr;
bool hasTextNodes = false;
for (const std::shared_ptr<QtGraphNode>& n : m_oldNodes)
{
if (n->isTextNode() && n->getName().size())
{
hasTextNodes = true;
QChar start(n->getName()[0]);
if (start >= c)
{
node = n.get();
break;
}
}
}
if (!hasTextNodes)
{
return;
}
QtGraphicsView* view = getView();
if (!node)
{
view->ensureVisibleAnimated(QRectF(0, view->scene()->height() - 5, view->scene()->width(), 5), 100, 100);
}
else
{
Vec2i pos = node->getPosition();
Vec2i size = node->getSize();
view->ensureVisibleAnimated(QRectF(pos.x, pos.y, size.x, size.y + view->height() / 3 * 2), 100, 100);
}
}
void QtGraphView::switchToNewGraphData()
{
m_oldGraph = m_graph;
@@ -170,6 +223,11 @@ void QtGraphView::switchToNewGraphData()
doResize();
if (m_scrollToTop)
{
updateScrollBars();
}
// Manually hover the item below the mouse cursor.
QtGraphicsView* view = getView();
QtGraphNode* node = view->getNodeAtCursorPosition();
@@ -244,7 +302,7 @@ void QtGraphView::doRebuildGraph(
}
QPointF center = itemsBoundingRect(m_nodes).center();
Vec2i o = GraphPostprocessor::alignOnRaster(Vec2i(center.x(), center.y()));
Vec2i o = GraphViewStyle::alignOnRaster(Vec2i(center.x(), center.y()));
QPointF offset = QPointF(o.x, o.y);
m_sceneRectOffset = offset - center;
@@ -282,6 +340,9 @@ void QtGraphView::doRebuildGraph(
m_activeNode.reset();
}
m_scrollToTop = params.scrollToTop;
m_isIndexedList = params.isIndexedList;
if (params.animatedTransition && ApplicationSettings::getInstance()->getUseAnimations())
{
createTransition();
@@ -364,12 +425,17 @@ std::shared_ptr<QtGraphNode> QtGraphView::createNodeRecursive(
}
else if (node->isBundleNode())
{
newNode = std::make_shared<QtGraphNodeBundle>(node->tokenId, node->getBundledNodeCount(), node->name);
newNode = std::make_shared<QtGraphNodeBundle>(
node->tokenId, node->getBundledNodeCount(), node->bundledNodeType, node->name);
}
else if (node->isQualifierNode())
{
newNode = std::make_shared<QtGraphNodeQualifier>(node->qualifierName);
}
else if (node->isTextNode())
{
newNode = std::make_shared<QtGraphNodeText>(node->name);
}
newNode->setPosition(node->position);
newNode->setSize(node->size);
@@ -384,7 +450,7 @@ std::shared_ptr<QtGraphNode> QtGraphView::createNodeRecursive(
{
newNode->setParent(parentNode);
}
else
else if (!node->isTextNode())
{
newNode->addComponent(std::make_shared<QtGraphNodeComponentMoveable>(newNode));
}
@@ -624,9 +690,9 @@ void QtGraphView::createTransition()
anim->setDuration(300);
if (!remainingNodes.size())
if (!remainingNodes.size() || m_scrollToTop)
{
connect(anim, SIGNAL(finished()), this, SLOT(centerScrollBars()));
connect(anim, SIGNAL(finished()), this, SLOT(updateScrollBars()));
}
remain->addAnimation(anim);
+4 -1
View File
@@ -49,9 +49,10 @@ public:
virtual Vec2i getViewSize() const;
private slots:
void centerScrollBars();
void updateScrollBars();
void finishedTransition();
void clickedInEmptySpace();
void pressedCharacterKey(QChar c);
private:
void switchToNewGraphData();
@@ -112,6 +113,8 @@ private:
std::list<std::shared_ptr<QtGraphNode>> m_oldNodes;
std::shared_ptr<QtGraphNode> m_activeNode;
bool m_scrollToTop;
bool m_isIndexedList;
std::shared_ptr<QSequentialAnimationGroup> m_transition;
QPointF m_sceneRectOffset;
@@ -5,15 +5,12 @@
#include <QGraphicsSceneEvent>
#include <QPen>
#include "utility/ResourcePaths.h"
#include "component/controller/helper/GraphPostprocessor.h"
#include "qt/graphics/QtRoundedRectItem.h"
#include "qt/utility/QtDeviceScaledPixmap.h"
#include "qt/utility/utilityQt.h"
#include "qt/view/graphElements/nodeComponents/QtGraphNodeComponent.h"
#include "qt/view/graphElements/QtGraphEdge.h"
#include "utility/ResourcePaths.h"
void QtGraphNode::blendIn()
{
@@ -250,6 +247,11 @@ bool QtGraphNode::isQualifierNode() const
return false;
}
bool QtGraphNode::isTextNode() const
{
return false;
}
Id QtGraphNode::getTokenId() const
{
return 0;
@@ -277,7 +279,7 @@ void QtGraphNode::addSubNode(const std::shared_ptr<QtGraphNode>& node)
void QtGraphNode::moved(const Vec2i& oldPosition)
{
setPosition(GraphPostprocessor::alignOnRaster(getPosition()));
setPosition(GraphViewStyle::alignOnRaster(getPosition()));
}
void QtGraphNode::onClick()
@@ -77,6 +77,7 @@ public:
virtual bool isExpandToggleNode() const;
virtual bool isBundleNode() const;
virtual bool isQualifierNode() const;
virtual bool isTextNode() const;
virtual Id getTokenId() const;
@@ -8,9 +8,10 @@
#include "component/view/GraphViewStyle.h"
#include "qt/graphics/QtCountCircleItem.h"
QtGraphNodeBundle::QtGraphNodeBundle(Id tokenId, size_t nodeCount, std::string name)
QtGraphNodeBundle::QtGraphNodeBundle(Id tokenId, size_t nodeCount, Node::NodeType type, std::string name)
: QtGraphNode()
, m_tokenId(tokenId)
, m_type(type)
{
this->setName(name);
@@ -38,12 +39,24 @@ Id QtGraphNodeBundle::getTokenId() const
void QtGraphNodeBundle::onClick()
{
MessageGraphNodeBundleSplit(m_tokenId).dispatch();
MessageGraphNodeBundleSplit(
m_tokenId,
m_type != Node::NODE_UNDEFINED && getName() != "Anonymous Namespaces",
m_type != Node::NODE_UNDEFINED
).dispatch();
}
void QtGraphNodeBundle::updateStyle()
{
GraphViewStyle::NodeStyle style = GraphViewStyle::getStyleOfBundleNode(m_isHovering);
GraphViewStyle::NodeStyle style;
if (m_type != Node::NODE_UNDEFINED)
{
style = GraphViewStyle::getStyleForNodeType(m_type, true, false, m_isHovering, false, false);
}
else
{
style = GraphViewStyle::getStyleOfBundleNode(m_isHovering);
}
setStyle(style);
m_circle->setPosition(Vec2f(m_rect->rect().right() - 3, m_rect->rect().top() + 3));
@@ -1,6 +1,7 @@
#ifndef QT_GRAPH_NODE_BUNDLE_H
#define QT_GRAPH_NODE_BUNDLE_H
#include "data/graph/Node.h"
#include "qt/view/graphElements/QtGraphNode.h"
class QtCountCircleItem;
@@ -9,7 +10,7 @@ class QtGraphNodeBundle
: public QtGraphNode
{
public:
QtGraphNodeBundle(Id tokenId, size_t nodeCount, std::string name);
QtGraphNodeBundle(Id tokenId, size_t nodeCount, Node::NodeType type, std::string name);
virtual ~QtGraphNodeBundle();
// QtGraphNode implementation
@@ -27,6 +28,7 @@ protected:
private:
QtCountCircleItem* m_circle;
Id m_tokenId;
Node::NodeType m_type;
};
#endif // QT_GRAPH_NODE_BUNDLE_H
@@ -0,0 +1,20 @@
#include "qt/view/graphElements/QtGraphNodeText.h"
QtGraphNodeText::QtGraphNodeText(const std::string& name)
{
setName(name);
}
QtGraphNodeText::~QtGraphNodeText()
{
}
bool QtGraphNodeText::isTextNode() const
{
return true;
}
void QtGraphNodeText::updateStyle()
{
setStyle(GraphViewStyle::getStyleOfTextNode());
}
@@ -0,0 +1,19 @@
#ifndef QT_GRAPH_NODE_TEXT_H
#define QT_GRAPH_NODE_TEXT_H
#include "qt/view/graphElements/QtGraphNode.h"
class QtGraphNodeText
: public QtGraphNode
{
public:
QtGraphNodeText(const std::string& name);
virtual ~QtGraphNodeText();
// QtGraphNode implementation
virtual bool isTextNode() const;
virtual void updateStyle();
};
#endif // QT_GRAPH_NODE_TEXT_H