From 3e6572347250ddb33cdf5ef461e1eba685587084 Mon Sep 17 00:00:00 2001 From: blobfish Date: Mon, 8 Jun 2015 10:52:55 -0400 Subject: [PATCH] Core: Gui: DAGView: skeleton implemented Need to enable through parameter BaseApp/Preferences/DAGView --- src/Gui/CMakeLists.txt | 7 + src/Gui/DAGView/DAGModel.cpp | 702 +++++++++++++++++++++++++++++++++++ src/Gui/DAGView/DAGModel.h | 354 ++++++++++++++++++ src/Gui/DAGView/DAGView.cpp | 117 ++++++ src/Gui/DAGView/DAGView.h | 75 ++++ src/Gui/MainWindow.cpp | 15 + src/Gui/Workbench.cpp | 8 + 7 files changed, 1278 insertions(+) create mode 100644 src/Gui/DAGView/DAGModel.cpp create mode 100644 src/Gui/DAGView/DAGModel.h create mode 100644 src/Gui/DAGView/DAGView.cpp create mode 100644 src/Gui/DAGView/DAGView.h diff --git a/src/Gui/CMakeLists.txt b/src/Gui/CMakeLists.txt index 01bbdf1d5..6c288ffa7 100644 --- a/src/Gui/CMakeLists.txt +++ b/src/Gui/CMakeLists.txt @@ -33,6 +33,7 @@ include_directories( ${CMAKE_CURRENT_BINARY_DIR}/propertyeditor ${CMAKE_CURRENT_BINARY_DIR}/TaskView ${CMAKE_CURRENT_BINARY_DIR}/Quarter + ${CMAKE_CURRENT_BINARY_DIR}/DAGView ${Boost_INCLUDE_DIRS} ${COIN3D_INCLUDE_DIRS} ${PYTHON_INCLUDE_DIRS} @@ -261,6 +262,8 @@ set(Gui_MOC_HDRS TaskView/TaskWatcher.h TaskView/TaskEditControl.h TaskView/TaskView.h + DAGView/DAGView.h + DAGView/DAGModel.h ) #qt4_wrap_cpp(Gui_MOC_SRCS ${Gui_MOC_HDRS}) fc_wrap_cpp(Gui_MOC_SRCS ${Gui_MOC_HDRS}) @@ -556,6 +559,8 @@ SET(Dock_Windows_CPP_SRCS Tree.cpp TreeView.cpp ProjectView.cpp + DAGView/DAGView.cpp + DAGView/DAGModel.cpp ) SET(Dock_Windows_HPP_SRCS CombiView.h @@ -568,6 +573,8 @@ SET(Dock_Windows_HPP_SRCS Tree.h TreeView.h ProjectView.h + DAGView/DAGView.h + DAGView/DAGModel.h ) SET(Dock_Windows_SRCS ${Dock_Windows_CPP_SRCS} diff --git a/src/Gui/DAGView/DAGModel.cpp b/src/Gui/DAGView/DAGModel.cpp new file mode 100644 index 000000000..a5589c1ff --- /dev/null +++ b/src/Gui/DAGView/DAGModel.cpp @@ -0,0 +1,702 @@ +/*************************************************************************** + * Copyright (c) 2015 Thomas Anderson * + * * + * This file is part of the FreeCAD CAx development system. * + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Library General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + * This library is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU Library General Public License for more details. * + * * + * You should have received a copy of the GNU Library General Public * + * License along with this library; see the file COPYING.LIB. If not, * + * write to the Free Software Foundation, Inc., 59 Temple Place, * + * Suite 330, Boston, MA 02111-1307, USA * + * * + ***************************************************************************/ + +#include "PreCompiled.h" +#ifndef _PreComp_ +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#endif + +#include +#include + +#include +#include +#include +#include +#include + +#include "DAGModel.h" + +using namespace Gui; +using namespace DAG; + +ViewEntryRectItem::ViewEntryRectItem(QGraphicsItem* parent) : QGraphicsRectItem(parent) +{ + selected = false; + preSelected = false; +} + +//I dont think I should have to call invalidate +//and definitely not on the whole scene! +//if we have performance problems, this will definitely +//be something to re-visit. I am not wasting anymore time on +//this right now. +// this->scene()->invalidate(); +// this->scene()->invalidate(this->sceneTransform().inverted().mapRect(this->boundingRect())); +// update(boundingRect()); +//note: I haven't tried this again since I turned BSP off. + +void ViewEntryRectItem::paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget) +{ + //TODO figure out how to mimic painting of itemviews. QStyle, QStyledItemDelegate. + + QBrush brush = backgroundBrush; + if (selected) + brush = selectionBrush; + if (preSelected) + brush = preSelectionBrush; + if (selected && preSelected) + brush = bothBrush; + + //heights are negative. + float radius = std::min(this->rect().width(), std::fabs(this->rect().height())) * 0.1; + painter->setBrush(brush); + painter->setPen(this->pen()); //should be Qt::NoPen. + painter->drawRoundedRect(this->rect(), radius, radius); + +// QGraphicsRectItem::paint(painter, option, widget); +} + +VertexProperty::VertexProperty() : + rectangle(new ViewEntryRectItem()), + point(new QGraphicsEllipseItem()), + icon(new QGraphicsPixmapItem()), + text(new QGraphicsTextItem()), + row(0), + column(0), + colorIndex(0), + lastVisibleState(VisibilityState::None) +{ + //All flags are disabled by default. + this->rectangle->setFlags(QGraphicsItem::ItemIsSelectable); + + //set z values. + this->rectangle->setZValue(-1000.0); + this->point->setZValue(1000.0); + this->icon->setZValue(0.0); + this->text->setZValue(0.0); +} + +const GraphLinkRecord& Model::findRecord(Vertex vertexIn) +{ + typedef GraphLinkContainer::index::type List; + const List &list = graphLink->get(); + List::const_iterator it = list.find(vertexIn); + assert(it != list.end()); + return *it; +} + +const GraphLinkRecord& Model::findRecord(const App::DocumentObject* dObjectIn) +{ + typedef GraphLinkContainer::index::type List; + const List &list = graphLink->get(); + List::const_iterator it = list.find(dObjectIn); + assert(it != list.end()); + return *it; +} + +const GraphLinkRecord& Model::findRecord(const ViewProviderDocumentObject* VPDObjectIn) +{ + typedef GraphLinkContainer::index::type List; + const List &list = graphLink->get(); + List::const_iterator it = list.find(VPDObjectIn); + assert(it != list.end()); + return *it; +} + +const GraphLinkRecord& Model::findRecord(const ViewEntryRectItem* rectIn) +{ + typedef GraphLinkContainer::index::type List; + const List &list = graphLink->get(); + List::const_iterator it = list.find(rectIn); + assert(it != list.end()); + return *it; +} + +const GraphLinkRecord& Model::findRecord(const std::string &stringIn) +{ + typedef GraphLinkContainer::index::type List; + const List &list = graphLink->get(); + List::const_iterator it = list.find(stringIn); + assert(it != list.end()); + return *it; +} + +void Model::eraseRecord(const ViewProviderDocumentObject* VPDObjectIn) +{ + typedef GraphLinkContainer::index::type List; + const List &list = graphLink->get(); + List::iterator it = list.find(VPDObjectIn); + assert(it != list.end()); + graphLink->get().erase(it); +} + +Model::Model(QObject *parentIn, const Gui::Document &documentIn) : QGraphicsScene(parentIn) +{ + //turned off BSP as it was giving inconsistent discovery of items + //underneath cursor. + this->setItemIndexMethod(QGraphicsScene::NoIndex); + + theGraph = std::shared_ptr(new Graph()); + graphLink = std::shared_ptr(new GraphLinkContainer()); + setupViewConstants(); + + graphDirty = false; + currentPrehighlight = nullptr; + + connectNewObject = documentIn.signalNewObject.connect(boost::bind(&Model::slotNewObject, this, _1)); + connectDelObject = documentIn.signalDeletedObject.connect(boost::bind(&Model::slotDeleteObject, this, _1)); + connectChgObject = documentIn.signalChangedObject.connect(boost::bind(&Model::slotChangeObject, this, _1, _2)); +} + +Model::~Model() +{ + if (connectNewObject.connected()) + connectNewObject.disconnect(); + if (connectDelObject.connected()) + connectDelObject.disconnect(); + if (connectChgObject.connected()) + connectChgObject.disconnect(); + + removeAllItems(); +} + +void Model::setupViewConstants() +{ + QFontMetrics fontMetric(qApp->font()); + fontHeight = fontMetric.height(); + verticalSpacing = 1.0; + rowHeight = (fontHeight + 2.0 * verticalSpacing) * -1.0; //pixel space top and bottom. + iconSize = fontHeight; + pointSize = fontHeight / 2.0; + pointSpacing = pointSize; + pointToIcon = iconSize; + rowPadding = fontHeight; + backgroundBrushes = {qApp->palette().base(), qApp->palette().alternateBase()}; + forgroundBrushes = + { + QBrush(Qt::red), + QBrush(Qt::darkRed), + QBrush(Qt::green), + QBrush(Qt::darkGreen), + QBrush(Qt::blue), + QBrush(Qt::darkBlue), + QBrush(Qt::cyan), + QBrush(Qt::darkCyan), + QBrush(Qt::magenta), + QBrush(Qt::darkMagenta), +// QBrush(Qt::yellow), can't read + QBrush(Qt::darkYellow), + QBrush(Qt::gray), + QBrush(Qt::darkGray), + QBrush(Qt::lightGray) + }; //reserve some of the these for highlight stuff. +} + +void Model::slotNewObject(const ViewProviderDocumentObject &VPDObjectIn) +{ + Vertex virginVertex = boost::add_vertex(*theGraph); + this->addItem((*theGraph)[virginVertex].rectangle.get()); + this->addItem((*theGraph)[virginVertex].point.get()); + this->addItem((*theGraph)[virginVertex].icon.get()); + this->addItem((*theGraph)[virginVertex].text.get()); + + GraphLinkRecord virginRecord; + virginRecord.DObject = VPDObjectIn.getObject(); + virginRecord.VPDObject = &VPDObjectIn; + virginRecord.rectItem = (*theGraph)[virginVertex].rectangle.get(); + virginRecord.uniqueName = std::string(virginRecord.DObject->getNameInDocument()); + virginRecord.vertex = virginVertex; + graphLink->insert(virginRecord); + + //construct pixmaps. + QIcon baseIcon = VPDObjectIn.getIcon(); + (*theGraph)[virginVertex].pixmapEnabled = baseIcon.pixmap(iconSize, iconSize, QIcon::Normal, QIcon::On); + (*theGraph)[virginVertex].pixmapDisabled = baseIcon.pixmap(iconSize, iconSize, QIcon::Disabled, QIcon::Off); + + //setup rectangle. + auto *rectangle = (*theGraph)[virginVertex].rectangle.get(); + rectangle->setPen(Qt::NoPen); + QColor preSelectionColor = qApp->palette().highlight().color(); + preSelectionColor.setAlphaF(0.25); + rectangle->setPreselectionBrush(QBrush(preSelectionColor)); + rectangle->setSelectionBrush(qApp->palette().highlight()); + QColor bothSelectionColor = qApp->palette().highlight().color(); + bothSelectionColor.setAlphaF(0.75); + rectangle->setBothBrush(QBrush(bothSelectionColor)); + + //setup point. + auto *point = (*theGraph)[virginVertex].point.get(); + point->setPen(Qt::NoPen); + + graphDirty = true; +} + +void Model::slotDeleteObject(const ViewProviderDocumentObject &VPDObjectIn) +{ + Vertex vertex = findRecord(&VPDObjectIn).vertex; + + //remove items from scene. + this->removeItem((*theGraph)[vertex].rectangle.get()); + this->removeItem((*theGraph)[vertex].point.get()); + this->removeItem((*theGraph)[vertex].icon.get()); + this->removeItem((*theGraph)[vertex].text.get()); + + //remove connector items + auto outRange = boost::out_edges(vertex, *theGraph); + for (auto outEdgeIt = outRange.first; outEdgeIt != outRange.second; ++outEdgeIt) + this->removeItem((*theGraph)[*outEdgeIt].connector.get()); + auto inRange = boost::in_edges(vertex, *theGraph); + for (auto inEdgeIt = inRange.first; inEdgeIt != inRange.second; ++inEdgeIt) + this->removeItem((*theGraph)[*inEdgeIt].connector.get()); + + //remove the actual vertex. + boost::clear_vertex(vertex, *theGraph); + boost::remove_vertex(vertex, *theGraph); + + eraseRecord(&VPDObjectIn); + graphDirty = true; +} + +void Model::slotChangeObject(const ViewProviderDocumentObject &VPDObjectIn, const App::Property& propertyIn) +{ + std::string name("Empty Name"); + if (propertyIn.getName()) //getName can return 0. + name = propertyIn.getName(); + assert(!name.empty()); + +// std::cout << std::endl << "inside changed object." << std::endl << +// "Property name is: " << name << std::endl << +// "Property type is: " << propertyIn.getTypeId().getName() << std::endl << std::endl; + + //renaming of objects. + if (std::string("Label") == name) + { + const GraphLinkRecord &record = findRecord(&VPDObjectIn); + auto *text = (*theGraph)[record.vertex].text.get(); + text->setPlainText(QString::fromUtf8(record.DObject->Label.getValue())); + } + + //link changes. these require a recalculation of connectors. + const static std::unordered_set linkTypes = + { + "App::PropertyLink", + "App::PropertyLinkList", + "App::PropertyLinkSub", + "App::PropertyLinkSubList" + }; + + if (linkTypes.find(propertyIn.getTypeId().getName()) != linkTypes.end()) + { + const GraphLinkRecord &record = findRecord(&VPDObjectIn); + boost::clear_vertex(record.vertex, *theGraph); + graphDirty = true; + } +} + +void Model::selectionChanged(const SelectionChanges& msg) +{ + //note that treeview uses set selection which sends a message with just a document name + //and no object name. Have to explore further. + + //lamda for clearing selections. + auto clearSelection = [this]() + { + BGL_FORALL_VERTICES_T(currentVertex, *theGraph, Graph) + { + ViewEntryRectItem *rect = (*theGraph)[currentVertex].rectangle.get(); + assert(rect); + rect->selectionOff(); + } + }; + + //lamda for getting rectangle. + auto getRectangle = [this](const char *in) + { + assert(in); + std::string name(in); + assert(!name.empty()); + const GraphLinkRecord &record = findRecord(name); + ViewEntryRectItem *rect = (*theGraph)[record.vertex].rectangle.get(); + assert(rect); + return rect; + }; + + if (msg.Type == SelectionChanges::AddSelection) + { + if (msg.pObjectName) + getRectangle(msg.pObjectName)->selectionOn(); + } + else if(msg.Type == SelectionChanges::RmvSelection) + { + if (msg.pObjectName) + getRectangle(msg.pObjectName)->selectionOff(); + } + else if(msg.Type == SelectionChanges::SetSelection) + { + clearSelection(); + + auto selections = Gui::Selection().getSelection(msg.pDocName); + for (const auto &selection : selections) + { + assert(selection.FeatName); + getRectangle(selection.FeatName)->selectionOn(); + } + } + else if(msg.Type == SelectionChanges::ClrSelection) + { + clearSelection(); + } + + this->invalidate(); +} + +void Model::awake() +{ + if (graphDirty) + { + updateSlot(); + this->invalidate(); + } + updateVisible(); +} + +void Model::updateSlot() +{ + Base::TimeInfo startTime; + + //here we will cycle through the graph updating edges. + //empty outList means it is a root. + //empty inList means it is a leaf. + + BGL_FORALL_VERTICES_T(currentVertex, *theGraph, Graph) + { + bool foundFirst = false; //temp hack. +#if 0 + //based on claim children. don't think this will be enough. + const auto *VPDObject = findRecord(currentVertex).VPDObject; + auto children = VPDObject->claimChildren(); + for (const auto *currentChild : children) + { + Vertex otherVertex = findRecord(currentChild).vertex; + bool result; + Edge edge; + boost::tie(edge, result) = boost::add_edge(currentVertex, otherVertex, *theGraph); + if (result) + { + if (!foundFirst) + { + (*theGraph)[edge].relation = EdgeProperty::BranchTag::Continue; + foundFirst = true; + } + else + (*theGraph)[edge].relation = EdgeProperty::BranchTag::Terminate; + + (*theGraph)[edge].connector = std::shared_ptr(new QGraphicsPathItem()); + (*theGraph)[edge].connector->setZValue(0.0); + this->addItem((*theGraph)[edge].connector.get()); + } + } +#else + //based on outlist. this won't be enough either. + const App::DocumentObject *currentDObject = findRecord(currentVertex).DObject; + std::vector otherDObjects = currentDObject->getOutList(); + for (auto ¤tOtherDObject : otherDObjects) + { + Vertex otherVertex = findRecord(currentOtherDObject).vertex; + bool result; + Edge edge; + boost::tie(edge, result) = boost::add_edge(currentVertex, otherVertex, *theGraph); + if (result) + { + if (!foundFirst) + { + (*theGraph)[edge].relation = EdgeProperty::BranchTag::Continue; + foundFirst = true; + } + else + (*theGraph)[edge].relation = EdgeProperty::BranchTag::Terminate; + + (*theGraph)[edge].connector = std::shared_ptr(new QGraphicsPathItem()); + (*theGraph)[edge].connector->setZValue(0.0); + this->addItem((*theGraph)[edge].connector.get()); + } + } +#endif + } + + indexVerticesEdges(); + Path sorted; + boost::topological_sort(*theGraph, std::back_inserter(sorted)); + int currentRow = 0; + int currentColumn = -1; //we know the first one will be a root so we can assume it will get kicked up to 0. + int maxColumn = currentColumn; //used for determining offset of icons and text. + float maxTextLength = 0; + for (const auto ¤tVertex : sorted) + { + if (boost::out_degree(currentVertex, *theGraph) == 0) + currentColumn++; + else + { + bool foundTarget = false; + OutEdgeIterator it, itEnd; + boost::tie(it, itEnd) = boost::out_edges(currentVertex, *theGraph); + for (;it != itEnd; ++it) + { + if ((*theGraph)[*it].relation == EdgeProperty::BranchTag::Continue) + { + Vertex target = boost::target(*it, *theGraph); + currentColumn = (*theGraph)[target].column; + foundTarget = true; + break; + } + } + if (!foundTarget) + currentColumn++; + } + + maxColumn = std::max(currentColumn, maxColumn); + QBrush currentBrush(forgroundBrushes.at(currentColumn % forgroundBrushes.size())); + + auto *rectangle = (*theGraph)[currentVertex].rectangle.get(); + rectangle->setRect(-rowPadding, 0.0, rowPadding, rowHeight); //calculate actual length later. + rectangle->setTransform(QTransform::fromTranslate(0, rowHeight * currentRow)); + rectangle->setBackgroundBrush(backgroundBrushes[currentRow % backgroundBrushes.size()]); + + auto *point = (*theGraph)[currentVertex].point.get(); + point->setRect(0.0, 0.0, -pointSize, -pointSize); + point->setTransform(QTransform::fromTranslate(pointSpacing * currentColumn, + rowHeight * currentRow + rowHeight / 2.0 + pointSize / 2.0)); + point->setBrush(currentBrush); + + auto *pixmap = (*theGraph)[currentVertex].icon.get(); + pixmap->setTransform(QTransform::fromTranslate(0.0, rowHeight * currentRow + rowHeight)); //calculate x location later. + + auto *text = (*theGraph)[currentVertex].text.get(); + text->setPlainText(QString::fromUtf8(findRecord(currentVertex).DObject->Label.getValue())); + maxTextLength = std::max(maxTextLength, static_cast(text->boundingRect().width())); + text->setTransform(QTransform::fromTranslate + (0.0, rowHeight * currentRow + rowHeight - verticalSpacing * 2.0)); //calculate x location later. + (*theGraph)[currentVertex].lastVisibleState = VisibilityState::None; //force visual update for color. + + //store column and row int the graph. use for connectors later. + (*theGraph)[currentVertex].row = currentRow; + (*theGraph)[currentVertex].column = currentColumn; + (*theGraph)[currentVertex].colorIndex = currentColumn % forgroundBrushes.size(); + + //our list is topo sorted so all dependents should be located, so we can build the connectors. + //will have some more logic for connector path, simple for now. + float currentX = pointSpacing * currentColumn - pointSize / 2.0; + float currentY = rowHeight * currentRow + rowHeight / 2.0; + OutEdgeIterator it, itEnd; + boost::tie(it, itEnd) = boost::out_edges(currentVertex, *theGraph); + for (; it != itEnd; ++it) + { + Vertex target = boost::target(*it, *theGraph); + float dependentX = pointSpacing * (*theGraph)[target].column - pointSize / 2.0; //on center. + float dependentY = rowHeight * (*theGraph)[target].row + rowHeight / 2.0; + + QGraphicsPathItem *pathItem = (*theGraph)[*it].connector.get(); + pathItem->setBrush(Qt::NoBrush); + QPainterPath path; + path.moveTo(currentX, currentY); + if (currentColumn != (*theGraph)[target].column) + path.lineTo(dependentX, currentY); + path.lineTo(dependentX, dependentY); //y is always different. + pathItem->setPath(path); + } + + currentRow++; + } + + float columnSpacing = (maxColumn * pointSpacing); + for (const auto ¤tVertex : sorted) + { + auto *pixmap = (*theGraph)[currentVertex].icon.get(); + QTransform iconTransform = QTransform::fromTranslate(columnSpacing + pointToIcon, 0.0); + pixmap->setTransform(pixmap->transform() * iconTransform); + + auto *text = (*theGraph)[currentVertex].text.get(); + QTransform textTransform = QTransform::fromTranslate(columnSpacing + pointToIcon + iconSize, 0.0); + text->setTransform(text->transform() * textTransform); + + auto *rectangle = (*theGraph)[currentVertex].rectangle.get(); + QRectF rect = rectangle->rect(); + rect.setWidth(columnSpacing + pointToIcon + iconSize + maxTextLength + 2.0 * rowPadding); + rectangle->setRect(rect); + } + + //Modeling_Challenge_Casting_ta4 with 59 features: "Initialize DAG View time: 0.007" +// std::cout << "Initialize DAG View time: " << Base::TimeInfo::diffTimeF(startTime, Base::TimeInfo()) << std::endl; + +// outputGraphviz(*theGraph, "./graphviz.dot"); + graphDirty = false; +} + +void Model::indexVerticesEdges() +{ + std::size_t index = 0; + + //index vertices. + VertexIterator it, itEnd; + for(boost::tie(it, itEnd) = boost::vertices(*theGraph); it != itEnd; ++it) + { + boost::put(boost::vertex_index, *theGraph, *it, index); + index++; + } + + //index edges. didn't need this when I put it in. + EdgeIterator eit, eitEnd; + index = 0; + for(boost::tie(eit, eitEnd) = boost::edges(*theGraph); eit != eitEnd; ++eit) + { + boost::put(boost::edge_index, *theGraph, *eit, index); + index++; + } +} + +void Model::removeAllItems() +{ + if (theGraph) + { + BGL_FORALL_VERTICES_T(currentVertex, *theGraph, Graph) + { + this->removeItem((*theGraph)[currentVertex].rectangle.get()); + this->removeItem((*theGraph)[currentVertex].point.get()); + this->removeItem((*theGraph)[currentVertex].icon.get()); + this->removeItem((*theGraph)[currentVertex].text.get()); + } + + BGL_FORALL_EDGES_T(currentEdge, *theGraph, Graph) + this->removeItem((*theGraph)[currentEdge].connector.get()); + } +} + +void Model::updateVisible() +{ + //not sure I want to use the same pixmap merge for failing feature icons. + //thinking maybe red background or another column of icons for state? + + BGL_FORALL_VERTICES_T(currentVertex, *theGraph, Graph) + { + const GraphLinkRecord &record = findRecord(currentVertex); + auto *text = (*theGraph)[currentVertex].text.get(); + auto *pixmap = (*theGraph)[currentVertex].icon.get(); + QIcon baseIcon = record.VPDObject->getIcon(); + VisibilityState currentState = (record.VPDObject->isShow()) ? (VisibilityState::On) : (VisibilityState::Off); + if + ( + (currentState != (*theGraph)[currentVertex].lastVisibleState) || + ((*theGraph)[currentVertex].lastVisibleState == VisibilityState::None) + ) + { + if (record.VPDObject->isShow()) + { + text->setDefaultTextColor(forgroundBrushes.at((*theGraph)[currentVertex].colorIndex).color()); + pixmap->setPixmap((*theGraph)[currentVertex].pixmapEnabled); + } + else + { + text->setDefaultTextColor(qApp->palette().color(QPalette::Disabled, QPalette::Text)); + pixmap->setPixmap((*theGraph)[currentVertex].pixmapDisabled); + } + (*theGraph)[currentVertex].lastVisibleState = currentState; + } + } +} + +ViewEntryRectItem* Model::getRectFromPosition(const QPointF& position) +{ + ViewEntryRectItem *rect = nullptr; + auto theItems = this->items(position, Qt::IntersectsItemBoundingRect, Qt::DescendingOrder); + for (auto *currentItem : theItems) + { + rect = dynamic_cast(currentItem); + if (rect) break; + } + + return rect; +} + +void Model::mouseMoveEvent(QGraphicsSceneMouseEvent* event) +{ + auto clearPrehighlight = [this]() + { + if (currentPrehighlight) + { + currentPrehighlight->preHighlightOff(); + currentPrehighlight = nullptr; + } + }; + + ViewEntryRectItem *rect = getRectFromPosition(event->scenePos()); + if (!rect) + { + clearPrehighlight(); + return; + } + + if (rect == currentPrehighlight) + return; + + clearPrehighlight(); + rect->preHighlightOn(); + currentPrehighlight = rect; + invalidate(); + + QGraphicsScene::mouseMoveEvent(event); +} + +void Model::mousePressEvent(QGraphicsSceneMouseEvent* event) +{ + ViewEntryRectItem *rect = getRectFromPosition(event->scenePos()); + if (rect) + { + const App::DocumentObject *dObject = findRecord(rect).DObject; + Gui::Selection().addSelection(dObject->getDocument()->getName(), dObject->getNameInDocument()); + } + + //need an else here to clear the selections. + //don't have current selection stored yet. + + QGraphicsScene::mousePressEvent(event); +} + + + + +#include diff --git a/src/Gui/DAGView/DAGModel.h b/src/Gui/DAGView/DAGModel.h new file mode 100644 index 000000000..9bbfa19d5 --- /dev/null +++ b/src/Gui/DAGView/DAGModel.h @@ -0,0 +1,354 @@ +/*************************************************************************** + * Copyright (c) 2015 Thomas Anderson * + * * + * This file is part of the FreeCAD CAx development system. * + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Library General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + * This library is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU Library General Public License for more details. * + * * + * You should have received a copy of the GNU Library General Public * + * License along with this library; see the file COPYING.LIB. If not, * + * write to the Free Software Foundation, Inc., 59 Temple Place, * + * Suite 330, Boston, MA 02111-1307, USA * + * * + ***************************************************************************/ + +#ifndef DAGMODEL_H +#define DAGMODEL_H + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +class QGraphicsSceneHoverEvent; + +namespace App{class DocumentObject;} + +namespace Gui +{ + class Document; + class ViewProviderDocumentObject; + class SelectionChanges; + + namespace DAG + { + /*all right I give up! the parenting combined with the zvalues is fubar! + * you can't control any kind of layering between children of separate parents + */ + class ViewEntryRectItem : public QGraphicsRectItem + { + public: + ViewEntryRectItem(QGraphicsItem* parent = 0); + void setBackgroundBrush(const QBrush &brushIn){backgroundBrush = brushIn;} + void setPreselectionBrush(const QBrush &brushIn){preSelectionBrush = brushIn;} + void setSelectionBrush(const QBrush &brushIn){selectionBrush = brushIn;} + void setBothBrush(const QBrush &brushIn){bothBrush = brushIn;} + void preHighlightOn(){preSelected = true;} + void preHighlightOff(){preSelected = false;} + void selectionOn(){selected = true;} + void selectionOff(){selected = false;} + bool isSelected(){return selected;} + bool isPreSelected(){return preSelected;} + protected: + virtual void paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget = 0); + private: + QBrush backgroundBrush; //!< brush used for background. not used yet. + QBrush selectionBrush; //!< brush used when selected. + QBrush preSelectionBrush; //!< brush used when pre selected. + QBrush bothBrush; //!< brush for when both selected and preSelected. + //start with booleans, may expand to state. + bool selected; + bool preSelected; + }; + + enum class VisibilityState + { + None = 0, // rectangle; //!< background + std::shared_ptr point; //!< point + std::shared_ptr icon; //!< icon + std::shared_ptr text; //!< text + int row; //!< row for this entry. + int column; //!< column number containing the point. + int colorIndex; //!< index in forground brushes + VisibilityState lastVisibleState; //!< visibility test. + QPixmap pixmapEnabled; + QPixmap pixmapDisabled; + }; + /*! @brief boost data for each vertex. + * + * needed to create an internal index for vertex. needed for listS. + * color is needed by some algorithms */ + typedef boost::property + < + boost::vertex_index_t, std::size_t, + boost::property + > vertex_prop; + + /*! @brief Graph edge information + * + * My data stored for each edge; + */ + struct EdgeProperty + { + //! Feature relation meta data. + enum class BranchTag + { + None = 0, //!< not defined. + Create, //!< create a new branch. + Continue, //!< continue a branch. + Terminate //!< terminate a branch. + }; + BranchTag relation; + std::shared_ptr connector; //!< line representing link between nodes. + }; + /*! @brief needed to create an internal index for graph edges. needed for setS.*/ + typedef boost::property edge_prop; + + typedef boost::adjacency_list Graph; + typedef boost::graph_traits::vertex_descriptor Vertex; + typedef boost::graph_traits::edge_descriptor Edge; + typedef boost::graph_traits::vertex_iterator VertexIterator; + typedef boost::graph_traits::edge_iterator EdgeIterator; + typedef boost::graph_traits::in_edge_iterator InEdgeIterator; + typedef boost::graph_traits::out_edge_iterator OutEdgeIterator; + typedef boost::graph_traits::adjacency_iterator VertexAdjacencyIterator; + typedef boost::reverse_graph GraphReversed; + typedef std::vector Path; //!< a path or any array of vertices + + template + class Edge_writer { + public: + Edge_writer(const GraphEW &graphEWIn) : graphEW(graphEWIn) {} + template + void operator()(std::ostream& out, const EdgeW& edgeW) const + { + out << "[label=\""; + out << "edge"; + out << "\"]"; + } + private: + const GraphEW &graphEW; + }; + + template + class Vertex_writer { + public: + Vertex_writer(const GraphVW &graphVWIn) : graphVW(graphVWIn) {} + template + void operator()(std::ostream& out, const VertexW& vertexW) const + { + out << "[label=\""; + out << graphVW[vertexW].text->toPlainText().toAscii().data(); + out << "\"]"; + } + private: + const GraphVW &graphVW; + }; + + template + void outputGraphviz(const GraphIn &graphIn, const std::string &filePath) + { + std::ofstream file(filePath.c_str()); + boost::write_graphviz(file, graphIn, Vertex_writer(graphIn), + Edge_writer(graphIn)); + } + + //! get all the leaves of the templated graph. Not used right now. + template + class RakeLeaves + { + typedef boost::graph_traits::vertex_descriptor GraphInVertex; + typedef std::vector GraphInVertices; + public: + RakeLeaves(const GraphIn &graphIn) : graph(graphIn) {} + GraphInVertices operator()() const + { + GraphInVertices out; + BGL_FORALL_VERTICES_T(currentVertex, graph, GraphIn) + { + if (boost::out_degree(currentVertex, graph) == 0) + out.push_back(currentVertex); + } + return out; + } + private: + const GraphIn &graph; + }; + + //! get all the roots of the templated graph. Not used right now. + template + class DigRoots + { + typedef boost::graph_traits::vertex_descriptor GraphInVertex; + typedef std::vector GraphInVertices; + public: + DigRoots(const GraphIn &graphIn) : graph(graphIn) {} + GraphInVertices operator()() const + { + GraphInVertices out; + BGL_FORALL_VERTICES_T(currentVertex, graph, GraphIn) + { + if (boost::in_degree(currentVertex, graph) == 0) + out.push_back(currentVertex); + } + return out; + } + private: + const GraphIn &graph; + }; + + /*! Multi_index record. */ + struct GraphLinkRecord + { + const App::DocumentObject *DObject; //!< document object + const Gui::ViewProviderDocumentObject *VPDObject; //!< view provider + const ViewEntryRectItem *rectItem; //!< qgraphics item. + std::string uniqueName; //!< name for document object. + Vertex vertex; //!< vertex in graph. + + //@{ + //! used as tags. + struct ByDObject{}; + struct ByVPDObject{}; + struct ByRectItem{}; + struct ByUniqueName{}; + struct ByVertex{}; + //@} + }; + + namespace BMI = boost::multi_index; + typedef boost::multi_index_container + < + GraphLinkRecord, + BMI::indexed_by + < + BMI::ordered_unique + < + BMI::tag, + BMI::member + >, + BMI::ordered_unique + < + BMI::tag, + BMI::member + >, + BMI::ordered_unique + < + BMI::tag, + BMI::member + >, + BMI::ordered_unique + < + BMI::tag, + BMI::member + >, + BMI::ordered_unique + < + BMI::tag, + BMI::member + > + > + > GraphLinkContainer; + + class Model : public QGraphicsScene + { + Q_OBJECT + public: + Model(QObject *parentIn, const Gui::Document &documentIn); + virtual ~Model() override; + void awake(); //!< hooked up to event dispatcher for update when idle. + void selectionChanged(const SelectionChanges& msg); + + protected: + virtual void mouseMoveEvent(QGraphicsSceneMouseEvent* event) override; + virtual void mousePressEvent(QGraphicsSceneMouseEvent* event) override; + + private Q_SLOTS: + void updateSlot(); + + private: + Model(){} + //documentObject slots. + typedef boost::BOOST_SIGNALS_NAMESPACE::connection Connection; + Connection connectNewObject; + Connection connectDelObject; + Connection connectChgObject; + Connection connectRenObject; + Connection connectActObject; + Connection connectEdtObject; + Connection connectResObject; + Connection connectHltObject; + Connection connectExpObject; + void slotNewObject(const Gui::ViewProviderDocumentObject &VPDObjectIn); + void slotDeleteObject(const Gui::ViewProviderDocumentObject &VPDObjectIn); + void slotChangeObject(const Gui::ViewProviderDocumentObject &VPDObjectIn, const App::Property& propertyIn); + + std::shared_ptr graphLink; + std::shared_ptr theGraph; + bool graphDirty; + + const GraphLinkRecord& findRecord(Vertex vertexIn); + const GraphLinkRecord& findRecord(const App::DocumentObject* dObjectIn); + const GraphLinkRecord& findRecord(const Gui::ViewProviderDocumentObject* VPDObjectIn); + const GraphLinkRecord& findRecord(const ViewEntryRectItem* rectIn); + const GraphLinkRecord& findRecord(const std::string &stringIn); + void eraseRecord(const Gui::ViewProviderDocumentObject* VPDObjectIn); + + void indexVerticesEdges(); + void removeAllItems(); + void updateVisible(); + + ViewEntryRectItem* getRectFromPosition(const QPointF &position); //!< can be nullptr + + //! @name View Constants for spacing + //@{ + float fontHeight; //!< height of the current qApp default font. + float verticalSpacing; //!< pixels between top and bottom of text to background rectangle. + float rowHeight; //!< height of background rectangle. + float iconSize; //!< size of icon to match font. + float pointSize; //!< size of the connection point. + float pointSpacing; //!< spacing between pofloat columns. + float pointToIcon; //!< spacing from last column points to icon. + float rowPadding; //!< spaces added to rectangle bacground width ends. + std::vector backgroundBrushes; //!< brushes to paint background rectangles. + std::vector forgroundBrushes; //!< brushes to paint points, connectors, text. + void setupViewConstants(); + //@} + + ViewEntryRectItem *currentPrehighlight; + }; + } +} + +#endif // DAGMODEL_H diff --git a/src/Gui/DAGView/DAGView.cpp b/src/Gui/DAGView/DAGView.cpp new file mode 100644 index 000000000..63c48dd22 --- /dev/null +++ b/src/Gui/DAGView/DAGView.cpp @@ -0,0 +1,117 @@ +/*************************************************************************** + * Copyright (c) 2015 Thomas Anderson * + * * + * This file is part of the FreeCAD CAx development system. * + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Library General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + * This library is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU Library General Public License for more details. * + * * + * You should have received a copy of the GNU Library General Public * + * License along with this library; see the file COPYING.LIB. If not, * + * write to the Free Software Foundation, Inc., 59 Temple Place, * + * Suite 330, Boston, MA 02111-1307, USA * + * * + ***************************************************************************/ + +#include "PreCompiled.h" +#ifndef _PreComp_ +#include +#include +#endif + +#include + +#include + +#include +#include +#include + +#include "DAGModel.h" +#include "DAGView.h" + +using namespace Gui; +using namespace DAG; + +DAG::DockWindow::DockWindow(Gui::Document* gDocumentIn, QWidget* parent): Gui::DockWindow(gDocumentIn, parent) +{ + dagView = new View(this); + QVBoxLayout *layout = new QVBoxLayout(); + layout->addWidget(dagView); + this->setLayout(layout); +} + +View::View(QWidget* parentIn): QGraphicsView(parentIn) +{ + Application::Instance->signalActiveDocument.connect(boost::bind(&View::slotActiveDocument, this, _1)); + Application::Instance->signalDeleteDocument.connect(boost::bind(&View::slotDeleteDocument, this, _1)); + + //just update the dagview when the gui process is idle. + connect(QAbstractEventDispatcher::instance(), SIGNAL(awake()), this, SLOT(awakeSlot())); +} + +View::~View() +{ + Application::Instance->signalActiveDocument.disconnect(boost::bind(&View::slotActiveDocument, this, _1)); + Application::Instance->signalDeleteDocument.disconnect(boost::bind(&View::slotDeleteDocument, this, _1)); +} + +void View::slotActiveDocument(const Document &documentIn) +{ + ModelMap::const_iterator it = modelMap.find(&documentIn); + if (it == modelMap.end()) + { + ModelMap::value_type entry(std::make_pair(&documentIn, std::shared_ptr(new Model(this, documentIn)))); + modelMap.insert(entry); + this->setScene(entry.second.get()); + } + else + { + this->setScene(it->second.get()); + } +} + +void View::slotDeleteDocument(const Document &documentIn) +{ + ModelMap::iterator it = modelMap.find(&documentIn); + if (it != modelMap.end()) + modelMap.erase(it); +} + +void View::awakeSlot() +{ + Model *model = dynamic_cast(this->scene()); + if (model) + model->awake(); +} + +void View::onSelectionChanged(const SelectionChanges& msg) +{ + //dispatch to appropriate document. + ModelMap::iterator it; + for (auto it = modelMap.begin(); it != modelMap.end(); ++it) + { + if (std::string(it->first->getDocument()->getName()) == std::string(msg.pDocName)) + { + it->second->selectionChanged(msg); + return; + } + } + + //why am I getting a spontanous event with an empty name? + std::ostringstream stream; + stream << std::endl << "couldn't find document of name: " << std::string(msg.pDocName) << std::endl << std::endl; + Base::Console().Warning(stream.str().c_str()); +// assert(0); //no document of name. +} + + + +#include "moc_DAGView.cpp" diff --git a/src/Gui/DAGView/DAGView.h b/src/Gui/DAGView/DAGView.h new file mode 100644 index 000000000..423dffb08 --- /dev/null +++ b/src/Gui/DAGView/DAGView.h @@ -0,0 +1,75 @@ +/*************************************************************************** + * Copyright (c) 2015 Thomas Anderson * + * * + * This file is part of the FreeCAD CAx development system. * + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Library General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + * This library is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU Library General Public License for more details. * + * * + * You should have received a copy of the GNU Library General Public * + * License along with this library; see the file COPYING.LIB. If not, * + * write to the Free Software Foundation, Inc., 59 Temple Place, * + * Suite 330, Boston, MA 02111-1307, USA * + * * + ***************************************************************************/ + +#ifndef DAGVIEW_H +#define DAGVIEW_H + +#include + +#include + +#include +#include +#include + +#include "DAGModel.h" + +namespace Gui +{ + namespace DAG + { + //! @brief view for DAG viewer + class View : public QGraphicsView, public SelectionObserver + { + Q_OBJECT + public: + View(QWidget *parentIn = 0); + virtual ~View() override; + + public Q_SLOTS: + void awakeSlot(); //!< hooked up to event dispatcher for update when idle. + + private: + virtual void onSelectionChanged(const SelectionChanges& msg) override; + + void slotActiveDocument(const Gui::Document &documentIn); + void slotDeleteDocument(const Gui::Document &documentIn); + + typedef std::map > ModelMap; + ModelMap modelMap; + }; + + //! @brief dock window for DAG viewer + class DockWindow : public Gui::DockWindow + { + Q_OBJECT + public: + DockWindow(Gui::Document* gDocumentIn = 0, QWidget *parent = 0); + ~DockWindow(){}; + + private: + View *dagView; + }; + } +} + +#endif // DAGVIEW_H diff --git a/src/Gui/MainWindow.cpp b/src/Gui/MainWindow.cpp index 625d07865..42cd045a9 100644 --- a/src/Gui/MainWindow.cpp +++ b/src/Gui/MainWindow.cpp @@ -98,6 +98,7 @@ #include "CombiView.h" #include "PythonConsole.h" #include "TaskView/TaskView.h" +#include "DAGView/DAGView.h" #include "DlgTipOfTheDayImp.h" #include "DlgUndoRedo.h" @@ -374,6 +375,20 @@ MainWindow::MainWindow(QWidget * parent, Qt::WindowFlags f) pcPython->setObjectName (QString::fromLatin1(QT_TRANSLATE_NOOP("QDockWidget","Python console"))); pDockMgr->registerDockWindow("Std_PythonView", pcPython); + + //Dag View. + //work through parameter. + ParameterGrp::handle group = App::GetApplication().GetUserParameter(). + GetGroup("BaseApp")->GetGroup("Preferences")->GetGroup("DAGView"); + bool enabled = group->GetBool("Enabled", false); + group->SetBool("Enabled", enabled); //ensure entry exists. + if (enabled) + { + DAG::DockWindow *dagDockWindow = new DAG::DockWindow(nullptr, this); + dagDockWindow->setObjectName + (QString::fromAscii(QT_TRANSLATE_NOOP("QDockWidget","DAG View"))); + pDockMgr->registerDockWindow("Std_DAGView", dagDockWindow); + } #if 0 //defined(Q_OS_WIN32) this portion of code is not able to run with a vanilla Qtlib build on Windows. // The MainWindowTabBar is used to show tabbed dock windows with icons diff --git a/src/Gui/Workbench.cpp b/src/Gui/Workbench.cpp index b65917c26..12f73a0d2 100644 --- a/src/Gui/Workbench.cpp +++ b/src/Gui/Workbench.cpp @@ -639,6 +639,14 @@ DockWindowItems* StdWorkbench::setupDockWindows() const root->addDockWidget("Std_CombiView", Qt::LeftDockWidgetArea, false, false); root->addDockWidget("Std_ReportView", Qt::BottomDockWidgetArea, true, true); root->addDockWidget("Std_PythonView", Qt::BottomDockWidgetArea, true, true); + + //Dagview through parameter. + ParameterGrp::handle group = App::GetApplication().GetUserParameter(). + GetGroup("BaseApp")->GetGroup("Preferences")->GetGroup("DAGView"); + bool enabled = group->GetBool("Enabled", false); + if (enabled) + root->addDockWidget("Std_DAGView", Qt::RightDockWidgetArea, false, false); + return root; }