#include "QtGraphView.h" #include #include #include #include #include #include #include #include #include #include #include #include #include "ApplicationSettings.h" #include "DummyEdge.h" #include "DummyNode.h" #include "GraphViewStyle.h" #include "MessageActivateTrail.h" #include "MessageCustomTrailShow.h" #include "MessageDeactivateEdge.h" #include "MessageRefreshUI.h" #include "MessageScrollGraph.h" #include "MessageStatus.h" #include "QtGraphEdge.h" #include "QtGraphNodeAccess.h" #include "QtGraphNodeBundle.h" #include "QtGraphNodeComponentClickable.h" #include "QtGraphNodeComponentMoveable.h" #include "QtGraphNodeData.h" #include "QtGraphNodeExpandToggle.h" #include "QtGraphNodeGroup.h" #include "QtGraphNodeQualifier.h" #include "QtGraphNodeText.h" #include "QtGraphicsView.h" #include "QtSelfRefreshIconButton.h" #include "QtViewWidgetWrapper.h" #include "ResourcePaths.h" #include "utilityQt.h" QtGraphView::QtGraphView(ViewLayout* viewLayout) : GraphView(viewLayout) , m_focusHandler(this) , m_centerActiveNode(false) , m_scrollToTop(false) , m_restoreScroll(false) , m_isIndexedList(false) { setWidgetWrapper(std::make_shared(new QFrame())); QWidget* widget = QtViewWidgetWrapper::getWidgetOfView(this); QBoxLayout* layout = new QBoxLayout(QBoxLayout::TopToBottom); layout->setContentsMargins(0, 0, 0, 0); layout->setSpacing(0); widget->setLayout(layout); QGraphicsScene* scene = new QGraphicsScene(widget); QtGraphicsView* view = new QtGraphicsView(&m_focusHandler, widget); view->setScene(scene); view->setDragMode(QGraphicsView::ScrollHandDrag); view->setRenderHints(QPainter::Antialiasing | QPainter::SmoothPixmapTransform); view->viewport()->setCursor(Qt::ArrowCursor); widget->layout()->addWidget(view); connect(view, &QtGraphicsView::emptySpaceClicked, this, &QtGraphView::clickedInEmptySpace); connect(view, &QtGraphicsView::resized, this, &QtGraphView::resized); connect(view, &QtGraphicsView::focusIn, [this]() { setNavigationFocus(true); }); connect(view, &QtGraphicsView::focusOut, [this]() { setNavigationFocus(false); }); m_scrollSpeedChangeListenerHorizontal.setScrollBar(view->horizontalScrollBar()); m_scrollSpeedChangeListenerVertical.setScrollBar(view->verticalScrollBar()); connect(view->horizontalScrollBar(), &QScrollBar::valueChanged, this, &QtGraphView::scrolled); connect(view->verticalScrollBar(), &QScrollBar::valueChanged, this, &QtGraphView::scrolled); // trail controls { QStackedLayout* stack = new QStackedLayout(); { m_expandButton = new QtSelfRefreshIconButton( QLatin1String(""), ResourcePaths::getGuiPath().concatenate(L"graph_view/images/graph.png"), "search/button"); m_expandButton->setObjectName(QStringLiteral("expand_button")); m_expandButton->setToolTip(QStringLiteral("show trail controls")); m_expandButton->setIconSize(QSize(16, 16)); m_expandButton->setGeometry(0, 0, 26, 26); connect(m_expandButton, &QPushButton::clicked, this, &QtGraphView::clickedExpand); stack->addWidget(m_expandButton); } { QWidget* ui = new QWidget(); ui->setGeometry(0, 0, 26, 210); stack->addWidget(ui); m_collapseButton = new QtSelfRefreshIconButton( QLatin1String(""), ResourcePaths::getGuiPath().concatenate(L"graph_view/images/graph_arrow.png"), "search/button", ui); m_collapseButton->setObjectName(QStringLiteral("collapse_button")); m_collapseButton->setToolTip(QStringLiteral("hide trail controls")); m_collapseButton->setIconSize(QSize(16, 16)); connect(m_collapseButton, &QPushButton::clicked, this, &QtGraphView::clickedCollapse); m_customTrailButton = new QtSelfRefreshIconButton( QLatin1String(""), FilePath(), "search/button", ui); m_customTrailButton->setObjectName(QStringLiteral("trail_button")); m_customTrailButton->setIconSize(QSize(16, 16)); m_customTrailButton->setToolTip(QStringLiteral("custom trail")); m_customTrailButton->setIconPath( ResourcePaths::getGuiPath().concatenate(L"graph_view/images/graph_custom.png")); connect( m_customTrailButton, &QPushButton::clicked, this, &QtGraphView::clickedCustomTrail); m_forwardTrailButton = new QtSelfRefreshIconButton( QLatin1String(""), FilePath(), "search/button", ui); m_forwardTrailButton->setObjectName(QStringLiteral("trail_button")); m_forwardTrailButton->setIconSize(QSize(16, 16)); connect( m_forwardTrailButton, &QPushButton::clicked, this, &QtGraphView::clickedForwardTrail); m_backwardTrailButton = new QtSelfRefreshIconButton( QLatin1String(""), FilePath(), "search/button", ui); m_backwardTrailButton->setObjectName(QStringLiteral("trail_button")); m_backwardTrailButton->setIconSize(QSize(16, 16)); connect( m_backwardTrailButton, &QPushButton::clicked, this, &QtGraphView::clickedBackwardTrail); m_trailDepthLabel = new QLabel(ui); m_trailDepthLabel->setObjectName(QStringLiteral("depth_label")); m_trailDepthLabel->setToolTip(QStringLiteral("adjust trail depth")); m_trailDepthLabel->setAlignment(Qt::AlignCenter); m_trailDepthSlider = new QSlider(Qt::Vertical, ui); m_trailDepthSlider->setObjectName(QStringLiteral("depth_slider")); m_trailDepthSlider->setToolTip(QStringLiteral("adjust trail depth")); m_trailDepthSlider->setMinimum(1); m_trailDepthSlider->setMaximum(26); m_trailDepthSlider->setValue(5); connect( m_trailDepthSlider, &QSlider::valueChanged, this, &QtGraphView::trailDepthChanged); connect( m_trailDepthSlider, &QSlider::sliderReleased, this, &QtGraphView::trailDepthUpdated); m_collapseButton->setGeometry(0, 0, 26, 20); m_customTrailButton->setGeometry(0, 22, 26, 26); m_backwardTrailButton->setGeometry(0, 50, 26, 26); m_forwardTrailButton->setGeometry(0, 78, 26, 26); m_trailDepthLabel->setGeometry(0, 106, 26, 26); m_trailDepthSlider->setGeometry(0, 132, 26, 100); } m_trailWidget = new QWidget(widget); m_trailWidget->setGeometry(8, 8, 26, 238); m_trailWidget->setLayout(stack); if (ApplicationSettings::getInstance()->getGraphControlsVisible()) { clickedExpand(); } else { clickedCollapse(); } updateTrailButtons(); trailDepthChanged(0); } // group controls { m_groupFileButton = new QtSelfRefreshIconButton( QLatin1String(""), ResourcePaths::getGuiPath().concatenate(L"graph_view/images/file.png"), "search/button"); m_groupNamespaceButton = new QtSelfRefreshIconButton( QLatin1String(""), ResourcePaths::getGuiPath().concatenate(L"graph_view/images/group_namespace.png"), "search/button"); m_groupFileButton->setObjectName(QStringLiteral("group_right_button")); m_groupNamespaceButton->setObjectName(QStringLiteral("group_left_button")); m_groupFileButton->setToolTip(QStringLiteral("group by file")); m_groupNamespaceButton->setToolTip(QStringLiteral("group by package/namespace/module")); 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::createWidgetWrapper() {} void QtGraphView::refreshView() { m_onQtThread([this]() { doResize(); QtGraphicsView* view = getView(); const std::string css = utility::getStyleSheet( ResourcePaths::getGuiPath().concatenate(L"graph_view/graph_view.css")); view->setStyleSheet(css.c_str()); view->setAppZoomFactor(GraphViewStyle::getZoomFactor()); m_trailWidget->setStyleSheet(css.c_str()); m_groupWidget->setStyleSheet(css.c_str()); updateTrailButtons(); }); } bool QtGraphView::isVisible() const { return QtViewWidgetWrapper::getWidgetOfView(this)->isVisible(); } void QtGraphView::findMatches(ScreenSearchSender* sender, const std::wstring& query) { m_onQtThread([sender, query, this]() { m_matchedNodes.clear(); for (QtGraphNode* node: m_oldNodes) { node->matchNameRecursive(query, &m_matchedNodes); } sender->foundMatches(this, m_matchedNodes.size()); }); } void QtGraphView::activateMatch(size_t matchIndex) { m_onQtThread([matchIndex, this]() { if (matchIndex >= m_matchedNodes.size()) { return; } QtGraphNode* node = m_matchedNodes[matchIndex]; node->setActiveMatch(true); node->updateStyle(); centerNode(node); }); } void QtGraphView::deactivateMatch(size_t matchIndex) { m_onQtThread([matchIndex, this]() { if (matchIndex >= m_matchedNodes.size()) { return; } QtGraphNode* node = m_matchedNodes[matchIndex]; node->setActiveMatch(false); node->updateStyle(); }); } void QtGraphView::clearMatches() { if (m_matchedNodes.empty()) { return; } m_onQtThread([this]() { for (QtGraphNode* node: m_matchedNodes) { node->removeNameMatch(); } m_matchedNodes.clear(); }); } void QtGraphView::rebuildGraph( std::shared_ptr graph, const std::vector>& nodes, const std::vector>& edges, const GraphParams params) { m_onQtThread([=]() { if (isTransitioning()) { m_transition->stop(); m_transition.reset(); finishedTransition(); } if (graph) { m_graph = graph; } m_matchedNodes.clear(); QGraphicsView* view = getView(); // create nodes size_t activeNodeCount = 0; for (unsigned int i = 0; i < nodes.size(); i++) { activeNodeCount += nodes[i]->getActiveSubNodeCount(); } Id oldActiveTokenId = m_oldActiveNode ? m_oldActiveNode->getTokenId() : 0; m_nodes.clear(); m_activeNodes.clear(); m_oldActiveNode = nullptr; m_virtualNodeRects.clear(); for (unsigned int i = 0; i < nodes.size(); i++) { QtGraphNode* node = createNodeRecursive( view, nullptr, nodes[i].get(), activeNodeCount > 1, !params.disableInteraction); if (node) { m_nodes.push_back(node); } } if (m_activeNodes.size() == 1) { m_oldActiveNode = m_activeNodes.front(); } Id newActiveTokenId = m_oldActiveNode ? m_oldActiveNode->getTokenId() : 0; // move graph to center QPointF center = itemsBoundingRect(m_nodes).center(); const Vec2i o = GraphViewStyle::alignOnRaster( Vec2i(static_cast(center.x()), static_cast(center.y()))); QPointF offset = QPointF(o.x, o.y); m_sceneRectOffset = offset - center; for (QtGraphNode* node: m_nodes) { node->setPos(node->pos() - offset); } m_edges.clear(); QtGraphEdge::clearFocusedEdges(); // create edges Graph::TrailMode trailMode = m_graph ? m_graph->getTrailMode() : Graph::TRAIL_NONE; std::set visibleEdgeIds; for (const std::shared_ptr& edge: edges) { if (!edge->data || !edge->data->isType(Edge::EDGE_AGGREGATION)) { createEdge( view, edge.get(), &visibleEdgeIds, trailMode, offset, params.bezierEdges, !params.disableInteraction); } } for (const std::shared_ptr& edge: edges) { if (edge->data && edge->data->isType(Edge::EDGE_AGGREGATION)) { createAggregationEdge(view, edge.get(), &visibleEdgeIds, !params.disableInteraction); } } // focus previously focused node if (params.tokenIdToFocus) { m_focusHandler.focusTokenId(m_nodes, m_edges, params.tokenIdToFocus); } else if (hasNavigationFocus()) { m_focusHandler.refocusNode(m_nodes, oldActiveTokenId, newActiveTokenId); } else { m_focusHandler.clear(); } m_centerActiveNode = params.centerActiveNode; m_scrollToTop = params.scrollToTop; m_isIndexedList = params.isIndexedList; if (params.animatedTransition && ApplicationSettings::getInstance()->getUseAnimations() && view->isVisible()) { createTransition(); } else { switchToNewGraphData(); } }); } void QtGraphView::clear() { m_onQtThread([this]() { m_focusHandler.clear(); m_oldActiveNode = nullptr; m_activeNodes.clear(); for (QtGraphNode* node: m_oldNodes) { node->deleteLater(); } for (QtGraphEdge* edge: m_oldEdges) { edge->deleteLater(); } m_nodes.clear(); m_edges.clear(); m_oldNodes.clear(); m_oldEdges.clear(); m_graph.reset(); m_oldGraph.reset(); m_matchedNodes.clear(); getView()->scene()->clear(); }); } void QtGraphView::coFocusTokenIds(const std::vector& focusedTokenIds) { m_onQtThread([=]() { for (const Id& tokenId: focusedTokenIds) { QtGraphNode* node = QtGraphNode::findNodeRecursive(m_oldNodes, tokenId); if (node && !node->getIsFocused()) { node->coFocusIn(); continue; } for (QtGraphEdge* edge: m_oldEdges) { if (edge->getData() && edge->getData()->getId() == tokenId) { edge->coFocusIn(); break; } } } }); } void QtGraphView::deCoFocusTokenIds(const std::vector& defocusedTokenIds) { m_onQtThread([=]() { for (const Id& tokenId: defocusedTokenIds) { QtGraphNode* node = QtGraphNode::findNodeRecursive(m_oldNodes, tokenId); if (node && !node->getIsFocused()) { node->coFocusOut(); continue; } for (QtGraphEdge* edge: m_oldEdges) { if (edge->getData() && edge->getData()->getId() == tokenId) { edge->coFocusOut(); break; } } } }); } void QtGraphView::resizeView() { m_onQtThread([this]() { doResize(); }); } Vec2i QtGraphView::getViewSize() const { QtGraphicsView* view = getView(); const float zoomFactor = view->getZoomFactor(); return Vec2i( static_cast((view->width() - 50) / zoomFactor), static_cast((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; m_scrollValues = Vec2i(xValue, yValue); } void QtGraphView::activateEdge(Id edgeId) { m_onQtThread([=]() { if (isTransitioning()) { m_transition->stop(); m_transition.reset(); finishedTransition(); } for (QtGraphEdge* edge: m_oldEdges) { edge->setIsActive(false); } for (QtGraphEdge* edge: m_oldEdges) { if (edge->getData() && edge->getData()->getId() == edgeId) { edge->setIsActive(true); break; } } }); } void QtGraphView::setNavigationFocus(bool focus) { if (m_hasFocus == focus) { return; } m_hasFocus = focus; m_onQtThread([this, focus]() { focusView(focus); if (focus) { m_focusHandler.focus(); } else { m_focusHandler.defocus(); } }); } bool QtGraphView::hasNavigationFocus() const { return m_hasFocus; } void QtGraphView::focusView(bool focusIn) { QtGraphicsView* view = getView(); view->blockSignals(true); if (focusIn) { view->setFocus(); } else { view->clearFocus(); } view->blockSignals(false); } const std::list& QtGraphView::getGraphNodes() const { if (isTransitioning()) { return m_nodes; } return m_oldNodes; } const std::list& QtGraphView::getGraphEdges() const { if (isTransitioning()) { return m_edges; } return m_oldEdges; } QtGraphNode* QtGraphView::getActiveNode() const { return m_oldActiveNode; } void QtGraphView::ensureNodeVisible(QtGraphNode* node) { QtGraphicsView* view = getView(); Vec4i r = node->getBoundingRect(); QRectF rect(r.x(), r.y(), r.z() - r.x(), r.w() - r.y()); if (rect.width() > view->width() - 100) { rect.setWidth(view->width() - 100); } if (rect.height() > view->height() - 100) { rect.setHeight(view->height() - 100); } view->ensureVisibleAnimated(rect, 100, 100); } void QtGraphView::updateScrollBars() { QGraphicsView* view = getView(); QScrollBar* hb = view->horizontalScrollBar(); QScrollBar* vb = view->verticalScrollBar(); if (m_restoreScroll) { performScroll(hb, m_scrollValues.x()); performScroll(vb, m_scrollValues.y()); } else if (m_scrollToTop) { vb->setValue(vb->minimum()); } else if (!m_centerActiveNode) { performScroll(hb, (hb->minimum() + hb->maximum()) / 2); performScroll(vb, (vb->minimum() + vb->maximum()) / 2); } if (m_scrollToTop || m_restoreScroll) { m_centerActiveNode = false; } m_restoreScroll = false; m_scrollToTop = false; } void QtGraphView::finishedTransition() { QGraphicsView* view = getView(); view->setInteractive(true); for (QtGraphNode* node: m_nodes) { node->showNodeRecursive(); } for (QtGraphEdge* edge: m_edges) { edge->setOpacity(1.0f); } switchToNewGraphData(); } void QtGraphView::clickedInEmptySpace() { std::vector activeEdges; for (QtGraphEdge* edge: m_oldEdges) { if (edge->getIsActive()) { activeEdges.push_back(edge); } } if (m_graph && m_graph->getTrailMode() != Graph::TRAIL_NONE) { for (QtGraphEdge* edge: activeEdges) { edge->setIsActive(false); } } else if (activeEdges.size() == 1) { MessageDeactivateEdge(false).dispatch(); } } void QtGraphView::scrolled(int) { QGraphicsView* view = getView(); MessageScrollGraph(view->horizontalScrollBar()->value(), view->verticalScrollBar()->value()) .dispatch(); } void QtGraphView::resized() { if (isTransitioning()) { return; } doResize(); } void QtGraphView::trailDepthChanged(int) { if (m_trailDepthSlider->value() == m_trailDepthSlider->maximum()) { m_trailDepthLabel->setText(QStringLiteral("inf")); } else { m_trailDepthLabel->setText(QString::number(m_trailDepthSlider->value())); } } void QtGraphView::trailDepthUpdated() { if (m_oldGraph->getTrailMode() != Graph::TRAIL_NONE) { activateTrail(m_oldGraph->hasTrailOrigin()); } } void QtGraphView::clickedCollapse() { dynamic_cast(m_trailWidget->layout())->setCurrentIndex(0); ApplicationSettings::getInstance()->setGraphControlsVisible(false); ApplicationSettings::getInstance()->save(); } void QtGraphView::clickedExpand() { dynamic_cast(m_trailWidget->layout())->setCurrentIndex(1); ApplicationSettings::getInstance()->setGraphControlsVisible(true); ApplicationSettings::getInstance()->save(); } void QtGraphView::clickedCustomTrail() { MessageCustomTrailShow().dispatch(); } void QtGraphView::clickedBackwardTrail() { activateTrail(false); } 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(); } MessageRefreshUI().noStyleReload().dispatch(); } void QtGraphView::performScroll(QScrollBar* scrollBar, int value) const { if (ApplicationSettings::getInstance()->getUseAnimations() && getView()->isVisible()) { QPropertyAnimation* anim = new QPropertyAnimation(scrollBar, "value"); anim->setDuration(300); anim->setStartValue(scrollBar->value()); anim->setEndValue(value); anim->setEasingCurve(QEasingCurve::InOutQuad); anim->start(); } else { scrollBar->setValue(value); } } MessageActivateTrail QtGraphView::getMessageActivateTrail(bool forward) { MessageActivateTrail message(0, 0, 0, 0, false); QtGraphNodeData* node = dynamic_cast(m_oldActiveNode); if (node) { Edge::TypeMask edgeTypes; bool horizontalLayout = true; if (node->getData()->getType().isInheritable()) { edgeTypes = Edge::EDGE_INHERITANCE | Edge::EDGE_TEMPLATE_SPECIALIZATION; horizontalLayout = false; } else if (node->getData()->getType().isCallable()) { edgeTypes = Edge::EDGE_CALL | Edge::EDGE_OVERRIDE; horizontalLayout = true; } else if (node->getData()->getType().isFile()) { edgeTypes = Edge::EDGE_INCLUDE; horizontalLayout = true; } else { return message; } Id tokenId = node->getData()->getId(); Id originId = forward ? tokenId : 0; Id targetId = forward ? 0 : tokenId; int depth = m_trailDepthSlider->value(); if (depth == m_trailDepthSlider->maximum()) { depth = 0; } return MessageActivateTrail(originId, targetId, edgeTypes, depth, horizontalLayout); } return message; } void QtGraphView::activateTrail(bool forward) { MessageActivateTrail message = getMessageActivateTrail(forward); if (message.edgeTypes) { message.dispatch(); } } void QtGraphView::updateTrailButtons() { MessageActivateTrail message = getMessageActivateTrail(false); m_backwardTrailButton->setEnabled(message.edgeTypes); m_forwardTrailButton->setEnabled(message.edgeTypes); m_trailDepthLabel->setEnabled(message.edgeTypes); m_trailDepthSlider->setEnabled(message.edgeTypes); std::wstring backwardImagePath = L"graph_left.png"; std::wstring forwardImagePath = L"graph_right.png"; if (message.edgeTypes & Edge::EDGE_CALL) { m_backwardTrailButton->setToolTip(QStringLiteral("show caller graph")); m_forwardTrailButton->setToolTip(QStringLiteral("show callee graph")); } else if (message.edgeTypes & Edge::EDGE_INHERITANCE) { m_backwardTrailButton->setToolTip(QStringLiteral("show base hierarchy")); m_forwardTrailButton->setToolTip(QStringLiteral("show derived hierarchy")); backwardImagePath = L"graph_up.png"; forwardImagePath = L"graph_down.png"; } else if (message.edgeTypes & Edge::EDGE_INCLUDE) { m_backwardTrailButton->setToolTip(QStringLiteral("show including files hierarchy")); m_forwardTrailButton->setToolTip(QStringLiteral("show included files hierarchy")); } else { m_backwardTrailButton->setToolTip(QStringLiteral("no trail for active symbol")); m_forwardTrailButton->setToolTip(QStringLiteral("no trail for active symbol")); } m_forwardTrailButton->setIconPath( ResourcePaths::getGuiPath().concatenate(L"graph_view/images/" + forwardImagePath)); m_backwardTrailButton->setIconPath( ResourcePaths::getGuiPath().concatenate(L"graph_view/images/" + backwardImagePath)); } void QtGraphView::switchToNewGraphData() { m_oldGraph = m_graph; for (QtGraphNode* node: m_oldNodes) { node->hide(); node->setParentItem(nullptr); node->deleteLater(); } for (QtGraphEdge* edge: m_oldEdges) { edge->hide(); edge->setParentItem(nullptr); edge->deleteLater(); } m_oldNodes = m_nodes; m_oldEdges = m_edges; m_nodes.clear(); m_edges.clear(); doResize(); if (m_scrollToTop || m_restoreScroll) { updateScrollBars(); } QtGraphicsView* view = getView(); // // Manually hover the item below the mouse cursor. // QtGraphNode* node = view->getNodeAtCursorPosition(); // if (node) // { // node->hoverEnter(); // } if (m_activeNodes.size()) { if (m_centerActiveNode) { centerNode(m_activeNodes.front()); } m_activeNodes.clear(); } if (hasNavigationFocus()) { m_focusHandler.focusInitialNode(); } // Repaint to make sure all artifacts are removed view->update(); updateTrailButtons(); if (m_oldGraph && m_oldGraph->getTrailMode() != Graph::TRAIL_NONE) { MessageStatus(L"Finished graph display").dispatch(); } } QtGraphicsView* QtGraphView::getView() const { QWidget* widget = QtViewWidgetWrapper::getWidgetOfView(this); QtGraphicsView* view = widget->findChild(QLatin1String("")); if (!view) { LOG_ERROR("Failed to get QGraphicsView"); } return view; } void QtGraphView::doResize() { getView()->setSceneRect(getSceneRect(m_oldNodes)); } QtGraphNode* QtGraphView::createNodeRecursive( QGraphicsView* view, QtGraphNode* parentNode, const DummyNode* node, bool multipleActive, bool interactive) { if (!node->visible) { return nullptr; } QtGraphNode* newNode = nullptr; if (node->isGraphNode()) { newNode = new QtGraphNodeData( &m_focusHandler, node->data, node->name, node->childVisible, node->getQualifierNode() != nullptr, interactive); } else if (node->isAccessNode()) { newNode = new QtGraphNodeAccess(node->accessKind); } else if (node->isExpandToggleNode()) { newNode = new QtGraphNodeExpandToggle( node->isExpanded(), static_cast(node->invisibleSubNodeCount)); } else if (node->isBundleNode()) { newNode = new QtGraphNodeBundle( &m_focusHandler, node->tokenId, node->getBundledNodeCount(), node->bundledNodeType, node->name, interactive); } else if (node->isQualifierNode()) { newNode = new QtGraphNodeQualifier(node->qualifierName); } else if (node->isTextNode()) { newNode = new QtGraphNodeText(node->name, node->fontSizeDiff); } else if (node->isGroupNode()) { newNode = new QtGraphNodeGroup( &m_focusHandler, node->tokenId, node->name, node->groupType, node->interactive && interactive); } else { LOG_ERROR("DummyNode is not valid"); return nullptr; } newNode->setPosition(node->position); newNode->setSize(node->size); newNode->setColumnSize(node->columnSize); newNode->setIsActive(node->active); newNode->setMultipleActive(multipleActive); if (interactive) { newNode->addComponent(std::make_shared(newNode)); } view->scene()->addItem(newNode); if (parentNode) { newNode->setParent(parentNode); } else if (!node->isTextNode() && interactive) { newNode->addComponent(std::make_shared(newNode)); } if (node->active) { m_activeNodes.push_back(newNode); } for (unsigned int i = 0; i < node->subNodes.size(); i++) { QtGraphNode* subNode = createNodeRecursive( view, newNode, node->subNodes[i].get(), multipleActive, interactive); if (subNode) { newNode->addSubNode(subNode); } } newNode->updateStyle(); return newNode; } QtGraphEdge* QtGraphView::createEdge( QGraphicsView* view, const DummyEdge* edge, std::set* visibleEdgeIds, Graph::TrailMode trailMode, QPointF pathOffset, bool useBezier, bool interactive) { if (!edge->visible) { return nullptr; } QtGraphNode* owner = QtGraphNode::findNodeRecursive(m_nodes, edge->ownerId); QtGraphNode* target = QtGraphNode::findNodeRecursive(m_nodes, edge->targetId); if (owner != nullptr && target != nullptr) { QtGraphEdge* qtEdge = new QtGraphEdge( &m_focusHandler, owner, target, edge->data, edge->getWeight(), edge->active, interactive, edge->layoutHorizontal, edge->getDirection()); if (trailMode != Graph::TRAIL_NONE) { std::vector path = edge->path; for (size_t i = 0; i < path.size(); i++) { path[i].x = static_cast(path[i].x - pathOffset.x()); path[i].z = static_cast(path[i].z - pathOffset.x()); path[i].y = static_cast(path[i].y - pathOffset.y()); path[i].w = static_cast(path[i].w - pathOffset.y()); } for (const Vec4i& rect: path) { m_virtualNodeRects.push_back( QRectF(QPointF(rect.x(), rect.y()), QPointF(rect.z(), rect.w()))); } qtEdge->setIsTrailEdge(path, trailMode == Graph::TRAIL_HORIZONTAL); } else if (useBezier) { qtEdge->setUseBezier(true); } qtEdge->updateLine(); owner->addOutEdge(qtEdge); target->addInEdge(qtEdge); view->scene()->addItem(qtEdge); if (edge->data) { visibleEdgeIds->insert(edge->data->getId()); } m_edges.push_back(qtEdge); return qtEdge; } return nullptr; } QtGraphEdge* QtGraphView::createAggregationEdge( QGraphicsView* view, const DummyEdge* edge, std::set* visibleEdgeIds, bool interactive) { if (!edge->visible) { return nullptr; } bool allVisible = true; std::set aggregationIds = edge->data->getComponent()->getAggregationIds(); for (Id edgeId: aggregationIds) { if (visibleEdgeIds->find(edgeId) == visibleEdgeIds->end()) { allVisible = false; break; } } if (allVisible) { return nullptr; } return createEdge(view, edge, visibleEdgeIds, Graph::TRAIL_NONE, QPointF(), false, interactive); } QRectF QtGraphView::itemsBoundingRect(const std::list& items) const { QRectF boundingRect; for (const QtGraphNode* item: items) { boundingRect |= item->sceneBoundingRect(); } return boundingRect; } QRectF QtGraphView::getSceneRect(const std::list& items) const { QRectF sceneRect = itemsBoundingRect(items); for (const QRectF& rect: m_virtualNodeRects) { sceneRect |= rect; } return sceneRect.adjusted(-75, -75, 75, 75).translated(m_sceneRectOffset); } void QtGraphView::centerNode(QtGraphNode* node) { QtGraphicsView* view = getView(); Vec2i pos = node->getPosition(); Vec2i size = node->getSize(); QRectF rect(pos.x, pos.y, size.x, size.y); if (rect.height() > view->height() - 200) { rect.setHeight(view->height() - 200); } if (rect.width() > view->width() - 200) { rect.setWidth(view->width() - 200); } view->ensureVisibleAnimated(rect, 100, 100); } void QtGraphView::compareNodesRecursive( std::list newSubNodes, std::list oldSubNodes, std::list* appearingNodes, std::list* vanishingNodes, std::vector>* remainingNodes) { for (std::list::iterator it = newSubNodes.begin(); it != newSubNodes.end(); it++) { bool remains = false; for (std::list::iterator it2 = oldSubNodes.begin(); it2 != oldSubNodes.end(); it2++) { if (((*it)->isDataNode() && (*it2)->isDataNode() && (*it)->getTokenId() == (*it2)->getTokenId()) || ((*it)->isAccessNode() && (*it2)->isAccessNode() && dynamic_cast(*it)->getAccessKind() == dynamic_cast(*it2)->getAccessKind()) || ((*it)->isExpandToggleNode() && (*it2)->isExpandToggleNode()) || ((*it)->isBundleNode() && (*it2)->isBundleNode() && (*it)->getTokenId() == (*it2)->getTokenId()) || ((*it)->isQualifierNode() && (*it2)->isQualifierNode() && (*it)->getTokenId() == (*it2)->getTokenId()) || ((*it)->isGroupNode() && (*it2)->isGroupNode() && (*it)->getName() == (*it2)->getName())) { remainingNodes->push_back(std::pair(*it, *it2)); compareNodesRecursive( (*it)->getSubNodes(), (*it2)->getSubNodes(), appearingNodes, vanishingNodes, remainingNodes); oldSubNodes.erase(it2); remains = true; break; } } if (!remains) { appearingNodes->push_back(*it); } } for (QtGraphNode* node: oldSubNodes) { vanishingNodes->push_back(node); } } void QtGraphView::createTransition() { std::list appearingNodes; std::list vanishingNodes; std::vector> remainingNodes; compareNodesRecursive(m_nodes, m_oldNodes, &appearingNodes, &vanishingNodes, &remainingNodes); if (!vanishingNodes.size() && !appearingNodes.size()) { bool nodesMoved = false; for (const std::pair& p: remainingNodes) { if (p.first->getPosition() != p.second->getPosition() && p.first->getSize() != p.second->getSize()) { nodesMoved = true; } } if (!nodesMoved) { switchToNewGraphData(); return; } } QGraphicsView* view = getView(); view->setInteractive(false); m_transition = std::make_shared(); // fade out if (vanishingNodes.size() || m_oldEdges.size()) { QParallelAnimationGroup* vanish = new QParallelAnimationGroup(); for (QtGraphNode* node: vanishingNodes) { QPropertyAnimation* anim = new QPropertyAnimation(node, QByteArrayLiteral("opacity")); anim->setDuration(300); anim->setStartValue(1.0f); anim->setEndValue(0.0f); vanish->addAnimation(anim); } for (QtGraphEdge* edge: m_oldEdges) { QPropertyAnimation* anim = new QPropertyAnimation(edge, QByteArrayLiteral("opacity")); anim->setDuration(150); anim->setStartValue(1.0f); anim->setEndValue(0.0f); vanish->addAnimation(anim); } m_transition->addAnimation(vanish); } // move and scale { QParallelAnimationGroup* remain = new QParallelAnimationGroup(); for (std::pair p: remainingNodes) { QtGraphNode* newNode = p.first; QtGraphNode* oldNode = p.second; QPropertyAnimation* anim = new QPropertyAnimation(oldNode, QByteArrayLiteral("pos")); anim->setDuration(300); anim->setStartValue(oldNode->pos()); anim->setEndValue(newNode->pos()); remain->addAnimation(anim); connect(anim, &QPropertyAnimation::finished, newNode, &QtGraphNode::showNode); connect(anim, &QPropertyAnimation::finished, oldNode, &QtGraphNode::hideNode); newNode->hide(); anim = new QPropertyAnimation(oldNode, QByteArrayLiteral("size")); anim->setDuration(300); anim->setStartValue(oldNode->size()); anim->setEndValue(newNode->size()); remain->addAnimation(anim); if (newNode->isAccessNode() && newNode->getSubNodes().size() == 0 && oldNode->getSubNodes().size() > 0) { dynamic_cast(oldNode)->hideLabel(); } } QPropertyAnimation* anim = new QPropertyAnimation(view, QByteArrayLiteral("sceneRect")); anim->setStartValue(view->sceneRect()); anim->setEndValue(getSceneRect(m_nodes)); anim->setDuration(300); if (!remainingNodes.size() || m_scrollToTop || m_restoreScroll) { connect(anim, &QPropertyAnimation::finished, this, &QtGraphView::updateScrollBars); } remain->addAnimation(anim); m_transition->addAnimation(remain); } // fade in if (appearingNodes.size() || m_edges.size()) { QParallelAnimationGroup* appear = new QParallelAnimationGroup(); for (QtGraphNode* node: appearingNodes) { QPropertyAnimation* anim = new QPropertyAnimation(node, "opacity"); anim->setDuration(300); anim->setStartValue(0.0f); anim->setEndValue(1.0f); appear->addAnimation(anim); connect(anim, &QPropertyAnimation::finished, node, &QtGraphNode::blendIn); node->blendOut(); } for (QtGraphEdge* edge: m_edges) { QPropertyAnimation* anim = new QPropertyAnimation(edge, "opacity"); anim->setDuration(150); anim->setStartValue(0.0f); anim->setEndValue(1.0f); appear->addAnimation(anim); edge->setOpacity(0.0f); } m_transition->addAnimation(appear); } connect( m_transition.get(), &QPropertyAnimation::finished, this, &QtGraphView::finishedTransition); m_transition->start(); } bool QtGraphView::isTransitioning() const { return m_transition && m_transition->currentTime() < m_transition->totalDuration(); }