/*************************************************************************** * 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 #include #include #include #include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include "DAGModel.h" using namespace Gui; using namespace DAG; LineEdit::LineEdit(QWidget* parentIn): QLineEdit(parentIn) { } void LineEdit::keyPressEvent(QKeyEvent *eventIn) { if (eventIn->key() == Qt::Key_Escape) { Q_EMIT rejectedSignal(); eventIn->accept(); return; } if ( (eventIn->key() == Qt::Key_Enter) || (eventIn->key() == Qt::Key_Return) ) { Q_EMIT acceptedSignal(); eventIn->accept(); return; } QLineEdit::keyPressEvent(eventIn); } //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. 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(); setupFilters(); graphDirty = false; currentPrehighlight = nullptr; ParameterGrp::handle group = App::GetApplication().GetUserParameter(). GetGroup("BaseApp")->GetGroup("Preferences")->GetGroup("DAGView"); selectionMode = static_cast(group->GetInt("SelectionMode", 0)); group->SetInt("SelectionMode", static_cast(selectionMode)); //ensure entry exists. QIcon temp(Gui::BitmapFactory().pixmap("dagViewVisible")); visiblePixmapEnabled = temp.pixmap(iconSize, iconSize, QIcon::Normal, QIcon::On); visiblePixmapDisabled = temp.pixmap(iconSize, iconSize, QIcon::Disabled, QIcon::Off); QIcon passIcon(Gui::BitmapFactory().pixmap("dagViewPass")); passPixmap = passIcon.pixmap(iconSize, iconSize); QIcon failIcon(Gui::BitmapFactory().pixmap("dagViewFail")); failPixmap = failIcon.pixmap(iconSize, iconSize); QIcon pendingIcon(Gui::BitmapFactory().pixmap("dagViewPending")); pendingPixmap = pendingIcon.pixmap(iconSize, iconSize); renameAction = new QAction(this); renameAction->setText(tr("Rename")); renameAction->setStatusTip(tr("Rename object")); renameAction->setShortcut(Qt::Key_F2); connect(renameAction, SIGNAL(triggered()), this, SLOT(onRenameSlot())); editingFinishedAction = new QAction(this); editingFinishedAction->setText(tr("Finish editing")); editingFinishedAction->setStatusTip(tr("Finish editing object")); connect(this->editingFinishedAction, SIGNAL(triggered()), this, SLOT(editingFinishedSlot())); 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)); connectEdtObject = documentIn.signalInEdit.connect(boost::bind(&Model::slotInEdit, this, _1)); connectResObject = documentIn.signalResetEdit.connect(boost::bind(&Model::slotResetEdit, this, _1)); } Model::~Model() { if (connectNewObject.connected()) connectNewObject.disconnect(); if (connectDelObject.connected()) connectDelObject.disconnect(); if (connectChgObject.connected()) connectChgObject.disconnect(); if(connectEdtObject.connected()) connectEdtObject.disconnect(); if(connectResObject.connected()) connectResObject.disconnect(); removeAllItems(); } void Model::setupFilters() { // filters.push_back(std::shared_ptr(new FilterOrigin())); // filters.push_back(std::shared_ptr(new FilterTyped("PartDesign::Body"))); // filters.push_back(std::shared_ptr(new FilterTyped("App::Part"))); } void Model::setupViewConstants() { ParameterGrp::handle group = App::GetApplication().GetUserParameter(). GetGroup("BaseApp")->GetGroup("Preferences")->GetGroup("DAGView"); //get font point size. int fontPointSize = group->GetInt("FontPointSize", 0); group->SetInt("FontPointSize", fontPointSize); //ensure entry exists. if (fontPointSize != 0) { QFont tempFont(this->font()); tempFont.setPointSize(fontPointSize); this->setFont(tempFont); } //get direction direction = group->GetFloat("Direction", 1.0); if (direction != -1.0 && direction != 1.0) direction = 1.0; group->SetFloat("Direction", direction); //ensure entry exists. QFontMetrics fontMetric(this->font()); fontHeight = fontMetric.height(); verticalSpacing = 1.0; rowHeight = (fontHeight + 2.0 * verticalSpacing) * direction; //pixel space top and bottom. iconSize = fontHeight; pointSize = fontHeight / 2.0; pointSpacing = pointSize; pointToIcon = iconSize; iconToIcon = iconSize * 0.25; iconToText = iconSize / 2.0; rowPadding = fontHeight; backgroundBrushes = {this->palette().base(), this->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); addVertexItemsToScene(virginVertex); 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); //setup rectangle. auto *rectangle = (*theGraph)[virginVertex].rectangle.get(); rectangle->setEditingBrush(QBrush(Qt::yellow)); (*theGraph)[virginVertex].icon->setPixmap(VPDObjectIn.getIcon().pixmap(iconSize, iconSize)); (*theGraph)[virginVertex].stateIcon->setPixmap(passPixmap); (*theGraph)[virginVertex].text->setFont(this->font()); graphDirty = true; //we are here before python objects are instantiated. so at this point //the getIcon method doesn't reflect the python override. //so we hack in a delay to get the latest icon and set it for the graphics item. lastAddedVertex = virginVertex; QTimer::singleShot(0, this, SLOT(iconUpdateSlot())); } void Model::iconUpdateSlot() { if (lastAddedVertex == Graph::null_vertex()) return; const ViewProviderDocumentObject *VPDObject = findRecord(lastAddedVertex, *graphLink).VPDObject; (*theGraph)[lastAddedVertex].icon->setPixmap(VPDObject->getIcon().pixmap(iconSize, iconSize)); lastAddedVertex = Graph::null_vertex(); this->invalidate(); } void Model::slotDeleteObject(const ViewProviderDocumentObject &VPDObjectIn) { Vertex vertex = findRecord(&VPDObjectIn, *graphLink).vertex; //remove items from scene. removeVertexItemsFromScene(vertex); //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()); if (vertex == lastAddedVertex) lastAddedVertex = Graph::null_vertex(); //remove the actual vertex. boost::clear_vertex(vertex, *theGraph); boost::remove_vertex(vertex, *theGraph); eraseRecord(&VPDObjectIn, *graphLink); 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, *graphLink); 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", "App::PropertyLinkPickList" }; if (linkTypes.find(propertyIn.getTypeId().getName()) != linkTypes.end()) { const GraphLinkRecord &record = findRecord(&VPDObjectIn, *graphLink); boost::clear_vertex(record.vertex, *theGraph); graphDirty = true; } } void Model::slotInEdit(const ViewProviderDocumentObject& VPDObjectIn) { RectItem *rect = (*theGraph)[findRecord(&VPDObjectIn, *graphLink).vertex].rectangle.get(); rect->editingStart(); this->invalidate(); } void Model::slotResetEdit(const ViewProviderDocumentObject& VPDObjectIn) { RectItem *rect = (*theGraph)[findRecord(&VPDObjectIn, *graphLink).vertex].rectangle.get(); rect->editingFinished(); this->invalidate(); } 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. auto getAllEdges = [this](const Vertex &vertexIn) { //is there really no function to get both in and out edges? std::vector out; OutEdgeIterator outIt, outItEnd; for (boost::tie(outIt, outItEnd) = boost::out_edges(vertexIn, *theGraph); outIt != outItEnd; ++outIt) out.push_back(*outIt); InEdgeIterator inIt, inItEnd; for (boost::tie(inIt, inItEnd) = boost::in_edges(vertexIn, *theGraph); inIt != inItEnd; ++inIt) out.push_back(*inIt); return out; }; auto highlightConnectorOn = [this, getAllEdges](const Vertex &vertexIn) { QColor color = (*theGraph)[vertexIn].text->defaultTextColor(); QPen pen(color); pen.setWidth(3.0); auto edges = getAllEdges(vertexIn); for (auto edge : edges) { (*theGraph)[edge].connector->setPen(pen); (*theGraph)[edge].connector->setZValue(1.0); } }; auto highlightConnectorOff = [this, getAllEdges](const Vertex &vertexIn) { auto edges = getAllEdges(vertexIn); for (auto edge : edges) { (*theGraph)[edge].connector->setPen(QPen()); (*theGraph)[edge].connector->setZValue(0.0); } }; //lamda for clearing selections. auto clearSelection = [this, highlightConnectorOff]() { BGL_FORALL_VERTICES(currentVertex, *theGraph, Graph) { RectItem *rect = (*theGraph)[currentVertex].rectangle.get(); assert(rect); rect->selectionOff(); highlightConnectorOff(currentVertex); } }; //lamda for getting rectangle. auto getRectangle = [this](const char *in) { assert(in); std::string name(in); assert(!name.empty()); const GraphLinkRecord &record = findRecord(name, *graphLink); RectItem *rect = (*theGraph)[record.vertex].rectangle.get(); assert(rect); return rect; }; if (msg.Type == SelectionChanges::AddSelection) { if (msg.pObjectName) { RectItem *rect = getRectangle(msg.pObjectName); rect->selectionOn(); highlightConnectorOn(findRecord(std::string(msg.pObjectName), *graphLink).vertex); } } else if(msg.Type == SelectionChanges::RmvSelection) { if (msg.pObjectName) { RectItem *rect = getRectangle(msg.pObjectName); rect->selectionOff(); highlightConnectorOff(findRecord(std::string(msg.pObjectName), *graphLink).vertex); } } else if(msg.Type == SelectionChanges::SetSelection) { clearSelection(); auto selections = Gui::Selection().getSelection(msg.pDocName); for (const auto &selection : selections) { assert(selection.FeatName); RectItem *rect = getRectangle(selection.FeatName); rect->selectionOn(); highlightConnectorOn(findRecord(selection.FeatName, *graphLink).vertex); } } else if(msg.Type == SelectionChanges::ClrSelection) { clearSelection(); } this->invalidate(); } void Model::awake() { if (graphDirty) { updateSlot(); this->invalidate(); } updateStates(); } void Model::updateSlot() { //empty outList means it is a root. //empty inList means it is a leaf. //NOTE: some of the following loops can/should be combined //for speed. Not doing yet, as I want a simple algorithm until //a more complete picture is formed. Base::TimeInfo startTime; //here we will cycle through the graph updating edges. //we have to do this first and in isolation because everything is dependent on an up to date graph. BGL_FORALL_VERTICES(currentVertex, *theGraph, Graph) { const App::DocumentObject *currentDObject = findRecord(currentVertex, *graphLink).DObject; std::vector otherDObjects = currentDObject->getOutList(); for (auto ¤tOtherDObject : otherDObjects) { Vertex otherVertex = findRecord(currentOtherDObject, *graphLink).vertex; bool result; Edge edge; boost::tie(edge, result) = boost::add_edge(currentVertex, otherVertex, *theGraph); if (result) { (*theGraph)[edge].connector = std::shared_ptr(new QGraphicsPathItem()); (*theGraph)[edge].connector->setZValue(0.0); } } } //apply filters. BGL_FORALL_VERTICES(currentVertex, *theGraph, Graph) { (*theGraph)[currentVertex].dagVisible = true; //default to shown. for (const auto ¤tFilter : filters) { if (!currentFilter->enabled || currentFilter->type != FilterBase::Type::Exclusion) continue; if (currentFilter->goFilter(currentVertex, *theGraph, *graphLink)) (*theGraph)[currentVertex].dagVisible = false; } } //inclusion takes precedence. Separate loop because filters might probe //children and parents. So we want to ensure all exclusions are done //before inclusions start. BGL_FORALL_VERTICES(currentVertex, *theGraph, Graph) { for (const auto ¤tFilter : filters) { if (!currentFilter->enabled || currentFilter->type != FilterBase::Type::Inclusion) continue; if (currentFilter->goFilter(currentVertex, *theGraph, *graphLink)) (*theGraph)[currentVertex].dagVisible = true; } } //sync scene items to graph vertex dagVisible. BGL_FORALL_VERTICES(currentVertex, *theGraph, Graph) { if ((*theGraph)[currentVertex].dagVisible && (!(*theGraph)[currentVertex].rectangle->scene())) addVertexItemsToScene(currentVertex); if ((!(*theGraph)[currentVertex].dagVisible) && (*theGraph)[currentVertex].rectangle->scene()) removeVertexItemsFromScene(currentVertex); } //sync scene items for graph edge. BGL_FORALL_EDGES(currentEdge, *theGraph, Graph) { Vertex source = boost::source(currentEdge, *theGraph); Vertex target = boost::target(currentEdge, *theGraph); bool edgeVisible = (*theGraph)[source].dagVisible && (*theGraph)[target].dagVisible; if (edgeVisible && (!(*theGraph)[currentEdge].connector->scene())) this->addItem((*theGraph)[currentEdge].connector.get()); if ((!edgeVisible) && (*theGraph)[currentEdge].connector->scene()) this->removeItem((*theGraph)[currentEdge].connector.get()); } indexVerticesEdges(); Path sorted; try { boost::topological_sort(*theGraph, std::back_inserter(sorted)); } catch(const boost::not_a_dag &) { Base::Console().Error("not a dag exception in DAGView::Model::updateSlot()\n"); return; } //index the vertices in sort order. int tempIndex = 0; for (const auto ¤tVertex : sorted) { (*theGraph)[currentVertex].topoSortIndex = tempIndex; tempIndex++; } //draw graph(nodes and connectors). int currentRow = 0; int currentColumn = -1; //we know first column is going to be root so will be kicked up to 0. int maxColumn = currentColumn; //used for determining offset of icons and text. float maxTextLength = 0; for (const auto ¤tVertex : sorted) { if (!(*theGraph)[currentVertex].dagVisible) continue; if (boost::out_degree(currentVertex, *theGraph) == 0) currentColumn = 0; else { //loop parents and find an acceptable column. int farthestParentIndex = sorted.size(); ColumnMask columnMask; Path parentVertices; OutEdgeIterator it, itEnd; boost::tie(it, itEnd) = boost::out_edges(currentVertex, *theGraph); for (;it != itEnd; ++it) { // std::cout << std::endl << "name: " << findRecord(currentVertex, *graphLink).DObject->Label.getValue() << std::endl; Vertex target = boost::target(*it, *theGraph); parentVertices.push_back(target); int currentParentIndex = (*theGraph)[target].topoSortIndex; if (currentParentIndex < farthestParentIndex) { Path::const_iterator start = sorted.begin() + currentParentIndex + 1; // 1 after Path::const_iterator end = sorted.begin() + (*theGraph)[currentVertex].topoSortIndex; // 1 before Path::const_iterator it; for (it = start; it != end; ++it) { // std::cout << " parent: " << findRecord(*it, *graphLink).DObject->Label.getValue() << std::endl; columnMask |= (*theGraph)[*it].column; } farthestParentIndex = currentParentIndex; } } //have to create a smaller subset to get through std::cout. // std::bitset<8> testSet; // for (unsigned int index = 0; index < testSet.size(); ++index) // testSet[index]= columnMask[index]; // std::cout << "mask for " << findRecord(currentVertex, *graphLink).DObject->Label.getValue() << " " << // testSet.to_string() << std::endl; //now we should have a mask representing the columns that are being used. //this is from the lowest parent, in the topo sort, to last entry. //try to use the same column as one of the parents.(*theGraph)[*it].column int destinationColumn = 0; //default to first column for (const auto ¤tParent : parentVertices) { if (((*theGraph)[currentParent].column & columnMask).none()) { //go with first visible parent for now. if (!(*theGraph)[currentParent].dagVisible) continue; destinationColumn = static_cast(columnFromMask((*theGraph)[currentParent].column)); break; } } //if destination not valid look for the first open column. if (columnMask.test(destinationColumn)) { for (std::size_t index = 0; index < columnMask.size(); ++index) { if (! columnMask.test(index)) { destinationColumn = index; break; } } } currentColumn = destinationColumn; } assert(currentColumn < static_cast(ColumnMask().size())); //temp limitation. 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); float cheat = 0.0; if (direction == -1) cheat = rowHeight; auto *visiblePixmap = (*theGraph)[currentVertex].visibleIcon.get(); visiblePixmap->setTransform(QTransform::fromTranslate(0.0, rowHeight * currentRow + cheat)); //calculate x location later. auto *statePixmap = (*theGraph)[currentVertex].stateIcon.get(); statePixmap->setTransform(QTransform::fromTranslate(0.0, rowHeight * currentRow + cheat)); //calculate x location later. auto *pixmap = (*theGraph)[currentVertex].icon.get(); pixmap->setTransform(QTransform::fromTranslate(0.0, rowHeight * currentRow + cheat)); //calculate x location later. auto *text = (*theGraph)[currentVertex].text.get(); text->setPlainText(QString::fromUtf8(findRecord(currentVertex, *graphLink).DObject->Label.getValue())); text->setDefaultTextColor(currentBrush.color()); maxTextLength = std::max(maxTextLength, static_cast(text->boundingRect().width())); text->setTransform(QTransform::fromTranslate (0.0, rowHeight * currentRow - verticalSpacing * 2.0 + cheat)); //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.reset().set((currentColumn)); //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); if (!(*theGraph)[target].dagVisible) continue; //we don't make it here if source isn't visible. So don't have to worry about that. float dependentX = pointSpacing * static_cast(columnFromMask((*theGraph)[target].column)) + pointSize / 2.0; //on center. columnFromMask((*theGraph)[target].column); 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 == static_cast(columnFromMask((*theGraph)[target].column))) path.lineTo(currentX, dependentY); //straight connector in y. else { //connector with bend. float radius = pointSpacing / 1.9; //no zero length line. path.lineTo(currentX, dependentY + radius * direction); float yPosition; if (direction == -1.0) yPosition = dependentY - 2.0 * radius; else yPosition = dependentY; float width = 2.0 * radius; float height = width; if (dependentX > currentX) //radius to the right. { QRectF arcRect(currentX, yPosition, width, height); path.arcTo(arcRect, 180.0, 90.0 * -direction); } else //radius to the left. { QRectF arcRect(currentX - 2.0 * radius, yPosition, width, height); path.arcTo(arcRect, 0.0, 90.0 * direction); } path.lineTo(dependentX, dependentY); } pathItem->setPath(path); } currentRow++; } //now that we have the graph drawn we know where to place icons and text. float columnSpacing = (maxColumn * pointSpacing); for (const auto ¤tVertex : sorted) { float localCurrentX = columnSpacing; localCurrentX += pointToIcon; auto *visiblePixmap = (*theGraph)[currentVertex].visibleIcon.get(); QTransform visibleIconTransform = QTransform::fromTranslate(localCurrentX, 0.0); visiblePixmap->setTransform(visiblePixmap->transform() * visibleIconTransform); localCurrentX += iconSize + iconToIcon; auto *statePixmap = (*theGraph)[currentVertex].stateIcon.get(); QTransform stateIconTransform = QTransform::fromTranslate(localCurrentX, 0.0); statePixmap->setTransform(statePixmap->transform() * stateIconTransform); localCurrentX += iconSize + iconToIcon; auto *pixmap = (*theGraph)[currentVertex].icon.get(); QTransform iconTransform = QTransform::fromTranslate(localCurrentX, 0.0); pixmap->setTransform(pixmap->transform() * iconTransform); localCurrentX += iconSize + iconToText; auto *text = (*theGraph)[currentVertex].text.get(); QTransform textTransform = QTransform::fromTranslate(localCurrentX, 0.0); text->setTransform(text->transform() * textTransform); auto *rectangle = (*theGraph)[currentVertex].rectangle.get(); QRectF rect = rectangle->rect(); rect.setWidth(localCurrentX + maxTextLength + 2.0 * rowPadding); rectangle->setRect(rect); } //Modeling_Challenge_Casting_ta4 with 59 features: "Initialize DAG View time: 0.007" //keeping algo simple with extra loops only added 0.002 to above number. // 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(currentVertex, *theGraph, Graph) removeVertexItemsFromScene(currentVertex); BGL_FORALL_EDGES(currentEdge, *theGraph, Graph) { if ((*theGraph)[currentEdge].connector->scene()) this->removeItem((*theGraph)[currentEdge].connector.get()); } } } void Model::addVertexItemsToScene(const Gui::DAG::Vertex& vertexIn) { //these are either all in or all out. so just test rectangle. if ((*theGraph)[vertexIn].rectangle->scene()) //already in the scene. return; this->addItem((*theGraph)[vertexIn].rectangle.get()); this->addItem((*theGraph)[vertexIn].point.get()); this->addItem((*theGraph)[vertexIn].visibleIcon.get()); this->addItem((*theGraph)[vertexIn].stateIcon.get()); this->addItem((*theGraph)[vertexIn].icon.get()); this->addItem((*theGraph)[vertexIn].text.get()); } void Model::removeVertexItemsFromScene(const Gui::DAG::Vertex& vertexIn) { //these are either all in or all out. so just test rectangle. if (!(*theGraph)[vertexIn].rectangle->scene()) //not in the scene. return; this->removeItem((*theGraph)[vertexIn].rectangle.get()); this->removeItem((*theGraph)[vertexIn].point.get()); this->removeItem((*theGraph)[vertexIn].visibleIcon.get()); this->removeItem((*theGraph)[vertexIn].stateIcon.get()); this->removeItem((*theGraph)[vertexIn].text.get()); this->removeItem((*theGraph)[vertexIn].icon.get()); } void Model::updateStates() { //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(currentVertex, *theGraph, Graph) { const GraphLinkRecord &record = findRecord(currentVertex, *graphLink); auto *visiblePixmap = (*theGraph)[currentVertex].visibleIcon.get(); VisibilityState currentVisibilityState = (record.VPDObject->isShow()) ? (VisibilityState::On) : (VisibilityState::Off); if ( (currentVisibilityState != (*theGraph)[currentVertex].lastVisibleState) || ((*theGraph)[currentVertex].lastVisibleState == VisibilityState::None) ) { if (record.VPDObject->isShow()) visiblePixmap->setPixmap(visiblePixmapEnabled); else visiblePixmap->setPixmap(visiblePixmapDisabled); (*theGraph)[currentVertex].lastVisibleState = currentVisibilityState; } FeatureState currentFeatureState = FeatureState::Pass; if (record.DObject->isError()) currentFeatureState = FeatureState::Fail; else if ((record.DObject->mustExecute() == 1)) currentFeatureState = FeatureState::Pending; if (currentFeatureState != (*theGraph)[currentVertex].lastFeatureState) { if (currentFeatureState == FeatureState::Pass) { (*theGraph)[currentVertex].stateIcon->setPixmap(passPixmap); } else { if (currentFeatureState == FeatureState::Fail) (*theGraph)[currentVertex].stateIcon->setPixmap(failPixmap); else (*theGraph)[currentVertex].stateIcon->setPixmap(pendingPixmap); } (*theGraph)[currentVertex].stateIcon->setToolTip(QString::fromLatin1(record.DObject->getStatusString())); (*theGraph)[currentVertex].lastFeatureState = currentFeatureState; } } } std::size_t Model::columnFromMask(const ColumnMask &maskIn) { std::string maskString = maskIn.to_string(); return maskString.size() - maskString.find("1") - 1; } RectItem* Model::getRectFromPosition(const QPointF& position) { RectItem *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; } }; RectItem *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) { auto goShiftSelect = [this, event]() { QPointF currentPickPoint = event->scenePos(); QGraphicsLineItem intersectionLine(QLineF(lastPick, currentPickPoint)); QListselection = collidingItems(&intersectionLine); for (auto currentItem = selection.begin(); currentItem != selection.end(); ++currentItem) { RectItem *rect = dynamic_cast(*currentItem); if (!rect) continue; const GraphLinkRecord &selectionRecord = findRecord(rect, *graphLink); Gui::Selection().addSelection(selectionRecord.DObject->getDocument()->getName(), selectionRecord.DObject->getNameInDocument()); } }; auto toggleSelect = [](const App::DocumentObject *dObjectIn, RectItem *rectIn) { if (rectIn->isSelected()) Gui::Selection().rmvSelection(dObjectIn->getDocument()->getName(), dObjectIn->getNameInDocument()); else Gui::Selection().addSelection(dObjectIn->getDocument()->getName(), dObjectIn->getNameInDocument()); }; if (proxy) renameAcceptedSlot(); if (event->button() == Qt::LeftButton) { RectItem *rect = getRectFromPosition(event->scenePos()); if (rect) { const GraphLinkRecord &record = findRecord(rect, *graphLink); //don't like that I am doing this again here after getRectFromPosition call. QGraphicsItem *item = itemAt(event->scenePos()); QGraphicsPixmapItem *pixmapItem = dynamic_cast(item); if (pixmapItem && (pixmapItem == (*theGraph)[record.vertex].visibleIcon.get())) { //get all selections, but for now just the current pick. if ((*theGraph)[record.vertex].lastVisibleState == VisibilityState::Off) const_cast(record.VPDObject)->show(); //const hack else const_cast(record.VPDObject)->hide(); //const hack return; } const App::DocumentObject *dObject = record.DObject; if (selectionMode == SelectionMode::Single) { if (event->modifiers() & Qt::ControlModifier) { toggleSelect(dObject, rect); } else if((event->modifiers() & Qt::ShiftModifier) && lastPickValid) { goShiftSelect(); } else { Gui::Selection().clearSelection(dObject->getDocument()->getName()); Gui::Selection().addSelection(dObject->getDocument()->getName(), dObject->getNameInDocument()); } } if (selectionMode == SelectionMode::Multiple) { if((event->modifiers() & Qt::ShiftModifier) && lastPickValid) { goShiftSelect(); } else { toggleSelect(dObject, rect); } } lastPickValid = true; lastPick = event->scenePos(); } else { lastPickValid = false; Gui::Selection().clearSelection(); //get document name? } } QGraphicsScene::mousePressEvent(event); } void Model::mouseDoubleClickEvent(QGraphicsSceneMouseEvent* event) { if (event->button() == Qt::LeftButton) { auto selections = getAllSelected(); if(selections.size() != 1) return; const GraphLinkRecord &record = findRecord(selections.front(), *graphLink); Gui::Document* doc = Gui::Application::Instance->getDocument(record.DObject->getDocument()); MDIView *view = doc->getActiveView(); if (view) getMainWindow()->setActiveWindow(view); const_cast(record.VPDObject)->doubleClicked(); } QGraphicsScene::mouseDoubleClickEvent(event); } std::vector Model::getAllSelected() { std::vector out; BGL_FORALL_VERTICES(currentVertex, *theGraph, Graph) { if ((*theGraph)[currentVertex].rectangle->isSelected()) out.push_back(currentVertex); } return out; } void Model::contextMenuEvent(QGraphicsSceneContextMenuEvent* event) { RectItem *rect = getRectFromPosition(event->scenePos()); if (rect) { const GraphLinkRecord &record = findRecord(rect, *graphLink); //don't like that I am doing this again here after getRectFromPosition call. QGraphicsItem *item = itemAt(event->scenePos()); QGraphicsPixmapItem *pixmapItem = dynamic_cast(item); if (pixmapItem && (pixmapItem == (*theGraph)[record.vertex].visibleIcon.get())) { visiblyIsolate(record.vertex); return; } if (!rect->isSelected()) { Gui::Selection().clearSelection(record.DObject->getDocument()->getName()); Gui::Selection().addSelection(record.DObject->getDocument()->getName(), record.DObject->getNameInDocument()); lastPickValid = true; lastPick = event->scenePos(); } MenuItem view; Gui::Application::Instance->setupContextMenu("Tree", &view); QMenu contextMenu; MenuManager::getInstance()->setupContextMenu(&view, contextMenu); //actions for only one selection. std::vector selections = getAllSelected(); if (selections.size() == 1) { contextMenu.addAction(renameAction); //when we have only one selection then we know it is rect from above. if (!rect->isEditing()) const_cast(record.VPDObject)->setupContextMenu (&contextMenu, this, SLOT(editingStartSlot())); //const hack. else contextMenu.addAction(editingFinishedAction); } if (contextMenu.actions().count() > 0) contextMenu.exec(event->screenPos()); } QGraphicsScene::contextMenuEvent(event); } void Model::onRenameSlot() { assert(proxy == nullptr); std::vector selections = getAllSelected(); assert(selections.size() == 1); LineEdit *lineEdit = new LineEdit(); auto *text = (*theGraph)[selections.front()].text.get(); lineEdit->setText(text->toPlainText()); connect(lineEdit, SIGNAL(acceptedSignal()), this, SLOT(renameAcceptedSlot())); connect(lineEdit, SIGNAL(rejectedSignal()), this, SLOT(renameRejectedSlot())); proxy = this->addWidget(lineEdit); proxy->setGeometry(text->sceneBoundingRect()); lineEdit->selectAll(); QTimer::singleShot(0, lineEdit, SLOT(setFocus())); } void Model::renameAcceptedSlot() { assert(proxy); std::vector selections = getAllSelected(); assert(selections.size() == 1); const GraphLinkRecord &record = findRecord(selections.front(), *graphLink); LineEdit *lineEdit = dynamic_cast(proxy->widget()); assert(lineEdit); const_cast(record.DObject)->Label.setValue(lineEdit->text().toUtf8().constData()); //const hack finishRename(); } void Model::renameRejectedSlot() { finishRename(); } void Model::finishRename() { assert(proxy); this->removeItem(proxy); proxy->deleteLater(); proxy = nullptr; this->invalidate(); } void Model::editingStartSlot() { QAction* action = qobject_cast(sender()); if (action) { int edit = action->data().toInt(); auto selections = getAllSelected(); assert(selections.size() == 1); const GraphLinkRecord &record = findRecord(selections.front(), *graphLink); Gui::Document* doc = Gui::Application::Instance->getDocument(record.DObject->getDocument()); MDIView *view = doc->getActiveView(); if (view) getMainWindow()->setActiveWindow(view); doc->setEdit(const_cast(record.VPDObject), edit); } } void Model::editingFinishedSlot() { auto selections = getAllSelected(); assert(selections.size() == 1); const GraphLinkRecord &record = findRecord(selections.front(), *graphLink); Gui::Document* doc = Gui::Application::Instance->getDocument(record.DObject->getDocument()); doc->commitCommand(); doc->resetEdit(); doc->getDocument()->recompute(); } void Model::visiblyIsolate(Gui::DAG::Vertex sourceIn) { auto buildSkipTypes = []() { std::vector out; Base::Type type; type = Base::Type::fromName("App::DocumentObjectGroup"); if (type != Base::Type::badType()) out.push_back(type); type = Base::Type::fromName("App::Part"); if (type != Base::Type::badType()) out.push_back(type); type = Base::Type::fromName("PartDesign::Body"); if (type != Base::Type::badType()) out.push_back(type); return out; }; auto testSkipType = [](const App::DocumentObject *dObject, const std::vector &types) { for (const auto ¤tType : types) { if (dObject->isDerivedFrom(currentType)) return true; } return false; }; indexVerticesEdges(); Path connectedVertices; ConnectionVisitor visitor(connectedVertices); boost::breadth_first_search(*theGraph, sourceIn, boost::visitor(visitor)); boost::breadth_first_search(boost::make_reverse_graph(*theGraph), sourceIn, boost::visitor(visitor)); //note source vertex is added twice to Path. Once for each search. static std::vector skipTypes = buildSkipTypes(); for (const auto ¤tVertex : connectedVertices) { const GraphLinkRecord &record = findRecord(currentVertex, *graphLink); if (testSkipType(record.DObject, skipTypes)) continue; const_cast(record.VPDObject)->hide(); //const hack } const GraphLinkRecord &sourceRecord = findRecord(sourceIn, *graphLink); if (!testSkipType(sourceRecord.DObject, skipTypes)) const_cast(sourceRecord.VPDObject)->show(); //const hack } #include