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
+90 -5
View File
@@ -15,6 +15,7 @@
#include "utility/messaging/type/MessageActivateTrail.h"
#include "utility/messaging/type/MessageDeactivateEdge.h"
#include "utility/messaging/type/MessageRefresh.h"
#include "utility/messaging/type/MessageScrollGraph.h"
#include "utility/messaging/type/MessageStatus.h"
#include "utility/ResourcePaths.h"
@@ -158,6 +159,52 @@ void QtGraphView::initView()
updateTrailButtons();
trailDepthChanged(0);
}
// group controls
{
m_groupFileButton = new QtSelfRefreshIconButton(
"", ResourcePaths::getGuiPath().concatenate(L"graph_view/images/file.png"), "search/button");
m_groupNamespaceButton = new QtSelfRefreshIconButton(
"", ResourcePaths::getGuiPath().concatenate(L"graph_view/images/group_namespace.png"), "search/button");
m_groupFileButton->setObjectName("group_right_button");
m_groupNamespaceButton->setObjectName("group_left_button");
m_groupFileButton->setToolTip("group by file");
m_groupNamespaceButton->setToolTip("group by package/namespace");
m_groupFileButton->setCheckable(true);
m_groupNamespaceButton->setCheckable(true);
m_groupFileButton->setIconSize(QSize(14, 14));
m_groupNamespaceButton->setIconSize(QSize(14, 14));
connect(m_groupFileButton, &QPushButton::clicked, [this](){ groupingUpdated(m_groupFileButton); });
connect(m_groupNamespaceButton, &QPushButton::clicked, [this]() { groupingUpdated(m_groupNamespaceButton); });
GroupType type = ApplicationSettings::getInstance()->getGraphGrouping();
if (type == GroupType::FILE)
{
m_groupFileButton->setChecked(true);
}
else if (type == GroupType::NAMESPACE)
{
m_groupNamespaceButton->setChecked(true);
}
m_groupWidget = new QWidget(widget);
m_groupWidget->setGeometry(38, 8, 54, 26);
QHBoxLayout* layout = new QHBoxLayout();
layout->setContentsMargins(0, 0, 0, 0);
layout->setSpacing(2);
layout->addWidget(m_groupNamespaceButton);
layout->addWidget(m_groupFileButton);
m_groupWidget->setLayout(layout);
}
}
void QtGraphView::refreshView()
@@ -173,6 +220,8 @@ void QtGraphView::refreshView()
view->setAppZoomFactor(GraphViewStyle::getZoomFactor());
m_trailWidget->setStyleSheet(css.c_str());
m_groupWidget->setStyleSheet(css.c_str());
updateTrailButtons();
});
}
@@ -407,7 +456,7 @@ void QtGraphView::defocusTokenIds(const std::vector<Id>& defocusedTokenIds)
for (const Id& tokenId : defocusedTokenIds)
{
QtGraphNode* node = findNodeRecursive(m_oldNodes, tokenId);
if (node && node->isDataNode())
if (node && (node->isDataNode() || node->isGroupNode()))
{
node->focusOut();
continue;
@@ -441,6 +490,20 @@ Vec2i QtGraphView::getViewSize() const
return Vec2i((view->width() - 50) / zoomFactor, (view->height() - 100) / zoomFactor);
}
GroupType QtGraphView::getGrouping() const
{
if (m_groupFileButton->isChecked())
{
return GroupType::FILE;
}
else if (m_groupNamespaceButton->isChecked())
{
return GroupType::NAMESPACE;
}
return GroupType::NONE;
}
void QtGraphView::scrollToValues(int xValue, int yValue)
{
m_restoreScroll = true;
@@ -560,9 +623,17 @@ void QtGraphView::pressedCharacterKey(QChar c)
const QtGraphNode* node = nullptr;
bool hasTextNodes = false;
for (const QtGraphNode* n : m_oldNodes)
std::vector<QtGraphNode*> nodes(m_oldNodes.begin(), m_oldNodes.end());
size_t i = 0;
while (i < nodes.size())
{
if (n->isTextNode() && n->getName().size())
QtGraphNode* n = nodes[i++];
if (n->isGroupNode())
{
nodes.insert(nodes.end(), n->getSubNodes().begin(), n->getSubNodes().end());
}
else if (n->isTextNode() && n->getName().size())
{
hasTextNodes = true;
QChar start(n->getName()[0]);
@@ -657,6 +728,20 @@ void QtGraphView::clickedForwardTrail()
activateTrail(true);
}
void QtGraphView::groupingUpdated(QPushButton* button)
{
(button == m_groupFileButton ? m_groupNamespaceButton : m_groupFileButton)->setChecked(false);
ApplicationSettings* appSettings = ApplicationSettings::getInstance().get();
if (appSettings->getGraphGrouping() != getGrouping())
{
appSettings->setGraphGrouping(getGrouping());
appSettings->save();
}
MessageRefresh().refreshUiOnly().noReloadStyle().dispatch();
}
MessageActivateTrail QtGraphView::getMessageActivateTrail(bool forward)
{
MessageActivateTrail message(0, 0, 0, 0, false);
@@ -868,7 +953,7 @@ QtGraphNode* QtGraphView::createNodeRecursive(
QtGraphNode* newNode = nullptr;
if (node->isGraphNode())
{
newNode = new QtGraphNodeData(node->data, node->name, node->childVisible, node->hasQualifier);
newNode = new QtGraphNodeData(node->data, node->name, node->childVisible, node->getQualifierNode() != nullptr);
}
else if (node->isAccessNode())
{
@@ -892,7 +977,7 @@ QtGraphNode* QtGraphView::createNodeRecursive(
}
else if (node->isGroupNode())
{
newNode = new QtGraphNodeGroup(node->tokenId, node->name, node->groupType);
newNode = new QtGraphNodeGroup(node->tokenId, node->name, node->groupType, node->interactive);
}
else
{
+7
View File
@@ -61,6 +61,7 @@ public:
virtual void resizeView();
virtual Vec2i getViewSize() const;
virtual GroupType getGrouping() const;
virtual void scrollToValues(int xValue, int yValue);
@@ -84,6 +85,8 @@ private slots:
void clickedBackwardTrail();
void clickedForwardTrail();
void groupingUpdated(QPushButton* button);
private:
MessageActivateTrail getMessageActivateTrail(bool forward);
void activateTrail(bool forward);
@@ -153,6 +156,10 @@ private:
QSlider* m_trailDepthSlider;
QLabel* m_trailDepthLabel;
QWidget* m_groupWidget;
QtSelfRefreshIconButton* m_groupFileButton;
QtSelfRefreshIconButton* m_groupNamespaceButton;
std::vector<QRectF> m_virtualNodeRects;
// Name matches
@@ -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