ui: Group graph nodes by file or namespace (issues #171, #439, #522)

* added buttons to enable grouping by file or namespace
* last used group settings saved to ApplicationSettings
* made groups hoverable/clickable on label only
* improved node hatching and override edge colors
* group overview lists

fortune cookie message = Seen with the eyes of love, everything gets a new meaning.
This commit is contained in:
Eberhard Graether
2018-04-09 00:09:22 +02:00
parent 20106d5ce1
commit 5303187bea
40 changed files with 956 additions and 197 deletions
@@ -88,8 +88,8 @@ Id QtGraphEdge::getTokenId() const
void QtGraphEdge::updateLine()
{
QtGraphNode* owner = m_owner;
QtGraphNode* target = m_target;
const QtGraphNode* owner = m_owner;
const QtGraphNode* target = m_target;
Edge::EdgeType type = (getData() ? getData()->getType() : Edge::EDGE_AGGREGATION);
GraphViewStyle::EdgeStyle style = GraphViewStyle::getStyleForEdgeType(type, m_isActive | m_isFocused, false, m_isTrailEdge);
@@ -97,8 +97,22 @@ void QtGraphEdge::updateLine()
Vec4i ownerRect = owner->getBoundingRect();
Vec4i targetRect = target->getBoundingRect();
Vec4i ownerParentRect = owner->getParentBoundingRect();
Vec4i targetParentRect = target->getParentBoundingRect();
Vec4i ownerParentRect;
Vec4i targetParentRect;
const QtGraphNode* ownerNonGroupParent = owner->getLastNonGroupParent();
const QtGraphNode* targetNonGroupParent = target->getLastNonGroupParent();
if (owner->getLastParent() == target->getLastParent() && owner->getLastParent()->isGroupNode())
{
ownerParentRect = ownerNonGroupParent->getBoundingRect();
targetParentRect = targetNonGroupParent->getBoundingRect();
}
else
{
ownerParentRect = owner->getLastParent()->getBoundingRect();
targetParentRect = target->getLastParent()->getBoundingRect();
}
if (m_useBezier)
{
@@ -142,7 +156,7 @@ void QtGraphEdge::updateLine()
bezier->setRoute(route);
bezier->setPivot(QtLineItemBase::PIVOT_MIDDLE);
if (owner->getLastParent() == target->getLastParent())
if (ownerNonGroupParent == targetNonGroupParent)
{
if (ownerRect.y() < target->getBoundingRect().y())
{
@@ -163,7 +177,7 @@ void QtGraphEdge::updateLine()
QtLineItemAngled* child = dynamic_cast<QtLineItemAngled*>(m_child);
if (owner->getIsActive() && owner->getLastParent() == target->getLastParent())
if (owner->getIsActive() && ownerNonGroupParent == targetNonGroupParent)
{
child->setOnBack(true);
}
@@ -172,7 +186,7 @@ void QtGraphEdge::updateLine()
{
child->setEarlyBend(true);
if (owner->getLastParent() == target->getLastParent() ||
if (ownerNonGroupParent == targetNonGroupParent ||
(type == Edge::EDGE_OVERRIDE &&
targetParentRect.z() + style.targetOffset.x + style.originOffset.x > ownerParentRect.x()))
{
@@ -185,7 +199,7 @@ void QtGraphEdge::updateLine()
}
if (type == Edge::EDGE_INHERITANCE || (type == Edge::EDGE_TEMPLATE_SPECIALIZATION &&
owner == owner->getLastParent() && target == target->getLastParent()))
owner == owner->getLastNonGroupParent() && target == target->getLastNonGroupParent()))
{
child->setRoute(QtLineItemBase::ROUTE_VERTICAL);
@@ -194,7 +208,8 @@ void QtGraphEdge::updateLine()
child->setEarlyBend(true);
}
}
else if (type != Edge::EDGE_AGGREGATION || owner != owner->getLastParent() || target != target->getLastParent())
else if (type != Edge::EDGE_AGGREGATION ||
owner != owner->getLastNonGroupParent() || target != target->getLastNonGroupParent())
{
child->setRoute(QtLineItemBase::ROUTE_HORIZONTAL);
}
@@ -250,13 +265,18 @@ void QtGraphEdge::setIsFocused(bool isFocused)
void QtGraphEdge::onClick()
{
Edge::EdgeType type = (getData() ? getData()->getType() : Edge::EDGE_AGGREGATION);
if (!getData() || m_owner->isGroupNode() || m_target->isGroupNode())
{
QtGraphNode* node =
((m_direction == TokenComponentAggregation::DIRECTION_BACKWARD) == (type != Edge::EDGE_INHERITANCE))
? m_owner : m_target;
QtGraphNode* node = (m_direction == TokenComponentAggregation::DIRECTION_BACKWARD ? m_owner : m_target);
if (m_owner->isGroupNode())
{
node = m_owner;
}
else if (m_target->isGroupNode())
{
node = m_target;
}
MessageGraphNodeBundleSplit(node->getTokenId()).dispatch();
}
else if (isTrailEdge())
@@ -61,13 +61,13 @@ QtGraphNode* QtGraphNode::getParent() const
return m_parentNode;
}
QtGraphNode* QtGraphNode::getLastParent() const
QtGraphNode* QtGraphNode::getLastParent(bool noGroups) const
{
QtGraphNode* node = const_cast<QtGraphNode*>(this);
while (true)
{
QtGraphNode* parent = dynamic_cast<QtGraphNode*>(node->parentItem());
if (!parent)
if (!parent || (noGroups && parent->isGroupNode()))
{
break;
}
@@ -76,6 +76,11 @@ QtGraphNode* QtGraphNode::getLastParent() const
return node;
}
QtGraphNode* QtGraphNode::getLastNonGroupParent() const
{
return getLastParent(true);
}
void QtGraphNode::setParent(QtGraphNode* parentNode)
{
m_parentNode = parentNode;
@@ -86,7 +91,7 @@ void QtGraphNode::setParent(QtGraphNode* parentNode)
}
}
std::list<QtGraphNode*> QtGraphNode::getSubNodes() const
const std::list<QtGraphNode*>& QtGraphNode::getSubNodes() const
{
return m_subNodes;
}
@@ -142,11 +147,6 @@ Vec4i QtGraphNode::getBoundingRect() const
return Vec4i(pos.x, pos.y, pos.x + size.x, pos.y + size.y);
}
Vec4i QtGraphNode::getParentBoundingRect() const
{
return getLastParent()->getBoundingRect();
}
void QtGraphNode::addOutEdge(QtGraphEdge* edge)
{
m_outEdges.push_back(edge);
@@ -348,7 +348,7 @@ void QtGraphNode::addSubNode(QtGraphNode* node)
if (node->getIsActive())
{
QtGraphNode* parent = this;
while (parent)
while (parent && !parent->isGroupNode())
{
parent->setZValue(-10.0f);
parent->m_text->setZValue(-9.0f);
@@ -37,10 +37,11 @@ public:
virtual ~QtGraphNode();
QtGraphNode* getParent() const;
QtGraphNode* getLastParent() const;
QtGraphNode* getLastParent(bool noGroups = false) const;
QtGraphNode* getLastNonGroupParent() const;
void setParent(QtGraphNode* parentNode);
std::list<QtGraphNode*> getSubNodes() const;
const std::list<QtGraphNode*>& getSubNodes() const;
Vec2i getPosition() const;
virtual bool setPosition(const Vec2i& position);
@@ -52,7 +53,6 @@ public:
void setSize(const QSize& size);
Vec4i getBoundingRect() const;
Vec4i getParentBoundingRect() const;
void addOutEdge(QtGraphEdge* edge);
void addInEdge(QtGraphEdge* edge);
@@ -55,11 +55,7 @@ void QtGraphNodeData::onClick()
return;
}
FilePath path = getFilePath();
MessageActivateNodes message;
message.addNode(m_data->getId(), path.empty() ? m_data->getNameHierarchy() : NameHierarchy(path.wstr(), NAME_DELIMITER_FILE));
message.dispatch();
MessageActivateNodes(m_data->getId()).dispatch();
}
void QtGraphNodeData::moved(const Vec2i& oldPosition)
@@ -1,24 +1,33 @@
#include "qt/view/graphElements/QtGraphNodeGroup.h"
#include <QBrush>
#include <QGraphicsRectItem>
#include <QGraphicsPolygonItem>
#include <QGraphicsSceneHoverEvent>
#include <QPainterPath>
#include <QPen>
#include "utility/messaging/type/MessageActivateNodes.h"
#include "utility/messaging/type/MessageFocusIn.h"
#include "utility/messaging/type/MessageFocusOut.h"
#include "utility/messaging/type/MessageGraphNodeBundleSplit.h"
#include "qt/graphics/QtRoundedRectItem.h"
QtGraphNodeGroup::QtGraphNodeGroup(Id tokenId, const std::wstring& name, NodeType::GroupType type)
QtGraphNodeGroup::QtGraphNodeGroup(
Id tokenId, const std::wstring& name, GroupType type, bool interactive
)
: m_tokenId(tokenId)
, m_type(type)
, m_interactive(interactive)
{
setAcceptHoverEvents(true);
if (interactive)
{
setAcceptHoverEvents(true);
}
setName(name);
setZValue(-10.0f);
m_rect->setZValue(-10.0f);
if (type == NodeType::GROUP_FRAMELESS)
if (type == GroupType::FRAMELESS)
{
m_rect->hide();
return;
@@ -29,25 +38,26 @@ QtGraphNodeGroup::QtGraphNodeGroup(Id tokenId, const std::wstring& name, NodeTyp
return;
}
m_background = new QtRoundedRectItem(this);
m_backgroundTopRight = new QGraphicsRectItem(this);
m_backgroundBottomLeft = new QGraphicsRectItem(this);
m_background->setZValue(-10.0f);
m_backgroundTopRight->setZValue(-10.0f);
m_backgroundBottomLeft->setZValue(-10.0f);
m_background = new QGraphicsPolygonItem(this);
m_background->setZValue(-3.f);
GraphViewStyle::NodeStyle style = GraphViewStyle::getStyleOfGroupNode(type, false);
GraphViewStyle::NodeMargins margins = GraphViewStyle::getMarginsOfGroupNode(type, true);
int width = style.textOffset.x * 2 + style.borderWidth + margins.charWidth * name.size();
int height = margins.top * 2 + margins.charHeight;
int height = margins.spacingA + margins.charHeight;
int radius = style.cornerRadius;
m_background->setRadius(style.cornerRadius);
m_background->setRect(0, 0, width, height);
QPainterPath path;
path.moveTo(width, 0);
path.lineTo(radius, 0);
path.arcTo(0, 0, 2 * radius, 2 * radius, 90, 90);
path.lineTo(0, height);
path.lineTo(width - radius, height);
path.arcTo(width - 2 * radius, height - 2 * radius, 2 * radius, 2 * radius, 270, 90);
path.closeSubpath();
m_backgroundTopRight->setRect(width - style.cornerRadius, 0, style.cornerRadius, style.cornerRadius);
m_backgroundBottomLeft->setRect(0, height - style.cornerRadius, style.cornerRadius, style.cornerRadius);
m_background->setPolygon(path.toFillPolygon());
}
QtGraphNodeGroup::~QtGraphNodeGroup()
@@ -66,24 +76,64 @@ Id QtGraphNodeGroup::getTokenId() const
void QtGraphNodeGroup::onClick()
{
MessageGraphNodeBundleSplit(m_tokenId).dispatch();
if (!m_interactive || !m_isHovering)
{
return;
}
if (m_type == GroupType::FILE || m_type == GroupType::NAMESPACE)
{
MessageActivateNodes(m_tokenId).dispatch();
}
else
{
MessageGraphNodeBundleSplit(m_tokenId).dispatch();
}
}
void QtGraphNodeGroup::updateStyle()
{
GraphViewStyle::NodeStyle style = GraphViewStyle::getStyleOfGroupNode(m_type, false);
GraphViewStyle::NodeStyle style = GraphViewStyle::getStyleOfGroupNode(m_type, m_isHovering);
if (m_background)
{
m_background->setBrush(QColor(style.color.border.c_str()));
m_background->setPen(QPen(Qt::transparent));
m_backgroundTopRight->setBrush(QColor(style.color.border.c_str()));
m_backgroundTopRight->setPen(QPen(Qt::transparent));
m_backgroundBottomLeft->setBrush(QColor(style.color.border.c_str()));
m_backgroundBottomLeft->setPen(QPen(Qt::transparent));
}
setStyle(style);
}
void QtGraphNodeGroup::hoverLeaveEvent(QGraphicsSceneHoverEvent* event)
{
if (m_type == GroupType::FILE || m_type == GroupType::NAMESPACE)
{
MessageFocusOut({ m_tokenId }).dispatch();
}
else
{
focusOut();
}
}
void QtGraphNodeGroup::hoverMoveEvent(QGraphicsSceneHoverEvent* event)
{
if (!m_background || m_background->contains(event->pos()))
{
if (!m_isHovering)
{
if (m_type == GroupType::FILE || m_type == GroupType::NAMESPACE)
{
MessageFocusIn({ m_tokenId }, TOOLTIP_ORIGIN_GRAPH).dispatch();
}
else
{
focusIn();
}
}
}
else if (m_isHovering)
{
hoverLeaveEvent(nullptr);
}
}
@@ -1,18 +1,17 @@
#ifndef QT_GRAPH_NODE_GROUP_H
#define QT_GRAPH_NODE_GROUP_H
#include "data/NodeType.h"
#include "data/GroupType.h"
#include "qt/view/graphElements/QtGraphNode.h"
class QGraphicsRectItem;
class QtRoundedRectItem;
class QGraphicsPolygonItem;
class QtGraphNodeGroup
: public QtGraphNode
{
Q_OBJECT
public:
QtGraphNodeGroup(Id tokenId, const std::wstring& name, NodeType::GroupType type);
QtGraphNodeGroup(Id tokenId, const std::wstring& name, GroupType type, bool interactive);
virtual ~QtGraphNodeGroup();
// QtGraphNode implementation
@@ -22,13 +21,16 @@ public:
virtual void onClick();
virtual void updateStyle();
protected:
virtual void hoverLeaveEvent(QGraphicsSceneHoverEvent* event);
virtual void hoverMoveEvent(QGraphicsSceneHoverEvent* event);
private:
Id m_tokenId;
NodeType::GroupType m_type;
GroupType m_type;
const bool m_interactive;
QtRoundedRectItem* m_background = nullptr;
QGraphicsRectItem* m_backgroundTopRight = nullptr;
QGraphicsRectItem* m_backgroundBottomLeft = nullptr;
QGraphicsPolygonItem* m_background = nullptr;
};
#endif // QT_GRAPH_NODE_GROUP_H