| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| |
|
| | #include <boost/graph/topological_sort.hpp> |
| | #include <boost_graph_reverse_graph.hpp> |
| | #include <memory> |
| | #include <QBrush> |
| | #include <QColor> |
| | #include <QGraphicsPixmapItem> |
| | #include <QGraphicsProxyWidget> |
| | #include <QGraphicsSceneContextMenuEvent> |
| | #include <QGraphicsSceneMouseEvent> |
| | #include <QKeyEvent> |
| | #include <QMenu> |
| | #include <QPainter> |
| | #include <QPen> |
| | #include <QString> |
| | #include <QTimer> |
| |
|
| | #include <Base/Console.h> |
| | #include <Base/TimeInfo.h> |
| | #include <App/Document.h> |
| | #include <Gui/Application.h> |
| | #include <Gui/BitmapFactory.h> |
| | #include <Gui/Document.h> |
| | #include <Gui/MainWindow.h> |
| | #include <Gui/MenuManager.h> |
| | #include <Gui/Selection/Selection.h> |
| | #include <Gui/ViewProviderDocumentObject.h> |
| |
|
| | #include "DAGModel.h" |
| |
|
| |
|
| | using namespace Gui; |
| | using namespace DAG; |
| | namespace sp = std::placeholders; |
| |
|
| | 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); |
| | } |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| |
|
| | Model::Model(QObject* parentIn, const Gui::Document& documentIn) |
| | : QGraphicsScene(parentIn) |
| | { |
| | |
| | |
| | this->setItemIndexMethod(QGraphicsScene::NoIndex); |
| |
|
| | theGraph = std::make_shared<Graph>(); |
| | graphLink = std::make_shared<GraphLinkContainer>(); |
| | setupViewConstants(); |
| |
|
| | graphDirty = false; |
| | currentPrehighlight = nullptr; |
| |
|
| | ParameterGrp::handle group = App::GetApplication() |
| | .GetUserParameter() |
| | .GetGroup("BaseApp") |
| | ->GetGroup("Preferences") |
| | ->GetGroup("DAGView"); |
| | selectionMode = static_cast<SelectionMode>(group->GetInt("SelectionMode", 0)); |
| | group->SetInt("SelectionMode", static_cast<int>(selectionMode)); |
| |
|
| | QIcon temp(Gui::BitmapFactory().iconFromTheme("dagViewVisible")); |
| | visiblePixmapEnabled = temp.pixmap(iconSize, iconSize, QIcon::Normal, QIcon::On); |
| | visiblePixmapDisabled = temp.pixmap(iconSize, iconSize, QIcon::Disabled, QIcon::Off); |
| |
|
| | QIcon passIcon(Gui::BitmapFactory().iconFromTheme("dagViewPass")); |
| | passPixmap = passIcon.pixmap(iconSize, iconSize); |
| | QIcon failIcon(Gui::BitmapFactory().iconFromTheme("dagViewFail")); |
| | failPixmap = failIcon.pixmap(iconSize, iconSize); |
| | QIcon pendingIcon(Gui::BitmapFactory().iconFromTheme("dagViewPending")); |
| | pendingPixmap = pendingIcon.pixmap(iconSize, iconSize); |
| |
|
| | renameAction = new QAction(this); |
| | renameAction->setText(tr("Rename")); |
| | renameAction->setStatusTip(tr("Renames the object")); |
| | #ifndef Q_OS_MAC |
| | renameAction->setShortcut(Qt::Key_F2); |
| | #endif |
| | connect(renameAction, &QAction::triggered, this, &Model::renameAcceptedSlot); |
| |
|
| | editingFinishedAction = new QAction(this); |
| | editingFinishedAction->setText(tr("Finish Editing")); |
| | editingFinishedAction->setStatusTip(tr("Finishes editing the object")); |
| | connect(this->editingFinishedAction, &QAction::triggered, this, &Model::editingFinishedSlot); |
| |
|
| | |
| | connectNewObject = documentIn.signalNewObject.connect( |
| | std::bind(&Model::slotNewObject, this, sp::_1) |
| | ); |
| | connectDelObject = documentIn.signalDeletedObject.connect( |
| | std::bind(&Model::slotDeleteObject, this, sp::_1) |
| | ); |
| | connectChgObject = documentIn.signalChangedObject.connect( |
| | std::bind(&Model::slotChangeObject, this, sp::_1, sp::_2) |
| | ); |
| | connectEdtObject = documentIn.signalInEdit.connect(std::bind(&Model::slotInEdit, this, sp::_1)); |
| | connectResObject = documentIn.signalResetEdit.connect( |
| | std::bind(&Model::slotResetEdit, this, sp::_1) |
| | ); |
| | |
| |
|
| | for (auto obj : documentIn.getDocument()->getObjects()) { |
| | auto vpd = freecad_cast<Gui::ViewProviderDocumentObject*>(documentIn.getViewProvider(obj)); |
| | if (vpd) { |
| | slotNewObject(*vpd); |
| | } |
| | } |
| | } |
| |
|
| | 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::setupViewConstants() |
| | { |
| | ParameterGrp::handle group = App::GetApplication() |
| | .GetUserParameter() |
| | .GetGroup("BaseApp") |
| | ->GetGroup("Preferences") |
| | ->GetGroup("DAGView"); |
| |
|
| | |
| | int fontPointSize = group->GetInt("FontPointSize", 0); |
| | group->SetInt("FontPointSize", fontPointSize); |
| | if (fontPointSize != 0) { |
| | QFont tempFont(this->font()); |
| | tempFont.setPointSize(fontPointSize); |
| | this->setFont(tempFont); |
| | } |
| |
|
| | |
| | direction = group->GetFloat("Direction", 1.0); |
| | if (direction != -1.0 && direction != 1.0) { |
| | direction = 1.0; |
| | } |
| | group->SetFloat("Direction", direction); |
| |
|
| | QFontMetrics fontMetric(this->font()); |
| | fontHeight = fontMetric.height(); |
| | verticalSpacing = 1.0; |
| | rowHeight = (fontHeight + 2.0 * verticalSpacing) * direction; |
| | 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::darkYellow), |
| | QBrush(Qt::gray), |
| | QBrush(Qt::darkGray), |
| | QBrush(Qt::lightGray) |
| | }; |
| | } |
| |
|
| | 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); |
| |
|
| | |
| | auto rectangle = (*theGraph)[virginVertex].rectangle.get(); |
| | rectangle->setEditingBrush(QBrush(Qt::yellow)); |
| |
|
| | auto icon = (*theGraph)[virginVertex].icon; |
| | icon->setPixmap(VPDObjectIn.getIcon().pixmap(iconSize, iconSize)); |
| | (*theGraph)[virginVertex].stateIcon->setPixmap(passPixmap); |
| | (*theGraph)[virginVertex].text->setFont(this->font()); |
| | |
| | (*theGraph)[virginVertex].connChangeIcon |
| | = const_cast<Gui::ViewProviderDocumentObject&>(VPDObjectIn) |
| | .signalChangeIcon.connect( |
| | std::bind(&Model::slotChangeIcon, this, boost::cref(VPDObjectIn), icon) |
| | ); |
| | |
| |
|
| | graphDirty = true; |
| | lastAddedVertex = Graph::null_vertex(); |
| | } |
| |
|
| | void Model::slotChangeIcon( |
| | const ViewProviderDocumentObject& VPDObjectIn, |
| | std::shared_ptr<QGraphicsPixmapItem> icon |
| | ) |
| | { |
| | icon->setPixmap(VPDObjectIn.getIcon().pixmap(iconSize, iconSize)); |
| | this->invalidate(); |
| | } |
| |
|
| | void Model::slotDeleteObject(const ViewProviderDocumentObject& VPDObjectIn) |
| | { |
| | Vertex vertex = findRecord(&VPDObjectIn, *graphLink).vertex; |
| |
|
| | |
| | removeVertexItemsFromScene(vertex); |
| |
|
| | |
| | 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(); |
| | } |
| |
|
| | (*theGraph)[vertex].connChangeIcon.disconnect(); |
| |
|
| | |
| | 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.hasName()) { |
| | name = propertyIn.getName(); |
| | } |
| | assert(!name.empty()); |
| |
|
| | |
| | |
| | |
| |
|
| | |
| | if (std::string("Label") == name) { |
| | if (hasRecord(&VPDObjectIn, *graphLink)) { |
| | const GraphLinkRecord& record = findRecord(&VPDObjectIn, *graphLink); |
| | auto text = (*theGraph)[record.vertex].text.get(); |
| | text->setPlainText(QString::fromUtf8(record.DObject->Label.getValue())); |
| | } |
| | } |
| | else if (propertyIn.isDerivedFrom<App::PropertyLinkBase>()) { |
| | if (hasRecord(&VPDObjectIn, *graphLink)) { |
| | 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) |
| | { |
| | |
| | |
| |
|
| | auto getAllEdges = [this](const Vertex& vertexIn) { |
| | |
| | std::vector<Edge> 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); |
| | } |
| | }; |
| |
|
| | |
| | auto clearSelection = [this, highlightConnectorOff]() { |
| | BGL_FORALL_VERTICES(currentVertex, *theGraph, Graph) |
| | { |
| | RectItem* rect = (*theGraph)[currentVertex].rectangle.get(); |
| | assert(rect); |
| | rect->selectionOff(); |
| | highlightConnectorOff(currentVertex); |
| | } |
| | }; |
| |
|
| | |
| | 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() |
| | { |
| | |
| | |
| |
|
| | |
| | |
| | |
| |
|
| | Base::TimeElapsed startTime; |
| |
|
| | |
| | |
| | BGL_FORALL_VERTICES(currentVertex, *theGraph, Graph) |
| | { |
| | const App::DocumentObject* currentDObject = findRecord(currentVertex, *graphLink).DObject; |
| | std::vector<App::DocumentObject*> otherDObjects = currentDObject->getOutList(); |
| | for (auto& currentOtherDObject : otherDObjects) { |
| | if (!hasRecord(currentOtherDObject, *graphLink)) { |
| | continue; |
| | } |
| | 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::make_shared<QGraphicsPathItem>(); |
| | (*theGraph)[edge].connector->setZValue(0.0); |
| | } |
| | } |
| | } |
| |
|
| | BGL_FORALL_VERTICES(currentVertex, *theGraph, Graph) |
| | { |
| | if (!(*theGraph)[currentVertex].rectangle->scene()) { |
| | addVertexItemsToScene(currentVertex); |
| | } |
| | } |
| |
|
| | |
| | BGL_FORALL_EDGES(currentEdge, *theGraph, Graph) |
| | { |
| | if (!(*theGraph)[currentEdge].connector->scene()) { |
| | this->addItem((*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"); |
| | |
| | graphDirty = false; |
| | return; |
| | } |
| | |
| | int tempIndex = 0; |
| | for (const auto& currentVertex : sorted) { |
| | (*theGraph)[currentVertex].topoSortIndex = tempIndex; |
| | tempIndex++; |
| | } |
| |
|
| | |
| | int currentRow = 0; |
| | int currentColumn = -1; |
| | int maxColumn = currentColumn; |
| | qreal maxTextLength = 0; |
| | for (const auto& currentVertex : sorted) { |
| | if (boost::out_degree(currentVertex, *theGraph) == 0) { |
| | currentColumn = 0; |
| | } |
| | else { |
| | |
| | int farthestParentIndex = sorted.size(); |
| | ColumnMask columnMask; |
| | Path parentVertices; |
| | OutEdgeIterator it, itEnd; |
| | boost::tie(it, itEnd) = boost::out_edges(currentVertex, *theGraph); |
| | for (; it != itEnd; ++it) { |
| | |
| | |
| |
|
| | 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; |
| | Path::const_iterator end = sorted.begin() |
| | + (*theGraph)[currentVertex].topoSortIndex; |
| | Path::const_iterator it; |
| | for (it = start; it != end; ++it) { |
| | |
| | |
| |
|
| | columnMask |= (*theGraph)[*it].column; |
| | } |
| | farthestParentIndex = currentParentIndex; |
| | } |
| | } |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| |
|
| | |
| | |
| |
|
| | |
| | int destinationColumn = 0; |
| | for (const auto& currentParent : parentVertices) { |
| | if (((*theGraph)[currentParent].column & columnMask).none()) { |
| | |
| | destinationColumn = static_cast<int>( |
| | columnFromMask((*theGraph)[currentParent].column) |
| | ); |
| | break; |
| | } |
| | } |
| | |
| | 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<int>(ColumnMask().size())); |
| |
|
| | 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); |
| | 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 * static_cast<qreal>(currentColumn), |
| | rowHeight * currentRow + rowHeight / 2.0 - pointSize / 2.0 |
| | ) |
| | ); |
| | point->setBrush(currentBrush); |
| |
|
| | qreal cheat = 0.0; |
| | if (direction == -1) { |
| | cheat = rowHeight; |
| | } |
| |
|
| | auto visiblePixmap = (*theGraph)[currentVertex].visibleIcon.get(); |
| | visiblePixmap->setTransform( |
| | QTransform::fromTranslate(0.0, rowHeight * currentRow + cheat) |
| | ); |
| |
|
| | auto statePixmap = (*theGraph)[currentVertex].stateIcon.get(); |
| | statePixmap->setTransform( |
| | QTransform::fromTranslate(0.0, rowHeight * currentRow + cheat) |
| | ); |
| |
|
| | auto pixmap = (*theGraph)[currentVertex].icon.get(); |
| | pixmap->setTransform( |
| | QTransform::fromTranslate(0.0, rowHeight * currentRow + cheat) |
| | ); |
| |
|
| | auto text = (*theGraph)[currentVertex].text.get(); |
| | text->setPlainText( |
| | QString::fromUtf8(findRecord(currentVertex, *graphLink).DObject->Label.getValue()) |
| | ); |
| | text->setDefaultTextColor(currentBrush.color()); |
| | maxTextLength = std::max(maxTextLength, text->boundingRect().width()); |
| | text->setTransform( |
| | QTransform::fromTranslate(0.0, rowHeight * currentRow - verticalSpacing * 2.0 + cheat) |
| | ); |
| | (*theGraph)[currentVertex].lastVisibleState = VisibilityState::None; |
| | |
| |
|
| | |
| | (*theGraph)[currentVertex].row = currentRow; |
| | (*theGraph)[currentVertex].column.reset().set((currentColumn)); |
| |
|
| | |
| | |
| | qreal currentX = pointSpacing * currentColumn + pointSize / 2.0; |
| | qreal 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); |
| | qreal dependentX = pointSpacing |
| | * static_cast<int>(columnFromMask((*theGraph)[target].column)) |
| | + pointSize / 2.0; |
| | columnFromMask((*theGraph)[target].column); |
| | qreal 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<int>(columnFromMask((*theGraph)[target].column))) { |
| | path.lineTo(currentX, dependentY); |
| | } |
| | else { |
| | |
| | qreal radius = pointSpacing / 1.9; |
| |
|
| | path.lineTo(currentX, dependentY + radius * direction); |
| |
|
| | qreal yPosition; |
| | if (direction == -1.0) { |
| | yPosition = dependentY - 2.0 * radius; |
| | } |
| | else { |
| | yPosition = dependentY; |
| | } |
| | qreal width = 2.0 * radius; |
| | qreal height = width; |
| | if (dependentX > currentX) |
| | { |
| | QRectF arcRect(currentX, yPosition, width, height); |
| | path.arcTo(arcRect, 180.0, 90.0 * -direction); |
| | } |
| | else |
| | { |
| | 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++; |
| | } |
| |
|
| | |
| | qreal columnSpacing = (maxColumn * pointSpacing); |
| | for (const auto& currentVertex : sorted) { |
| | qreal 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); |
| | } |
| |
|
| | |
| | |
| | |
| | |
| |
|
| | |
| | graphDirty = false; |
| | } |
| |
|
| | void Model::indexVerticesEdges() |
| | { |
| | std::size_t index = 0; |
| |
|
| | |
| | VertexIterator it, itEnd; |
| | for (boost::tie(it, itEnd) = boost::vertices(*theGraph); it != itEnd; ++it) { |
| | boost::put(boost::vertex_index, *theGraph, *it, index); |
| | index++; |
| | } |
| |
|
| | |
| | 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) |
| | { |
| | |
| | if ((*theGraph)[vertexIn].rectangle->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) |
| | { |
| | |
| | if (!(*theGraph)[vertexIn].rectangle->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() |
| | { |
| | |
| | |
| |
|
| | 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<RectItem*>(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)); |
| | QList<QGraphicsItem*> selection = collidingItems(&intersectionLine); |
| | for (auto currentItem : selection) { |
| | auto rect = dynamic_cast<RectItem*>(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); |
| |
|
| | |
| | QGraphicsItem* item = itemAt(event->scenePos(), QTransform()); |
| | auto pixmapItem = dynamic_cast<QGraphicsPixmapItem*>(item); |
| | if (pixmapItem && (pixmapItem == (*theGraph)[record.vertex].visibleIcon.get())) { |
| | |
| | if ((*theGraph)[record.vertex].lastVisibleState == VisibilityState::Off) { |
| | const_cast<ViewProviderDocumentObject*>(record.VPDObject)->show(); |
| | } |
| | else { |
| | const_cast<ViewProviderDocumentObject*>(record.VPDObject)->hide(); |
| | } |
| |
|
| | 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(); |
| | } |
| | } |
| |
|
| | 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<ViewProviderDocumentObject*>(record.VPDObject)->doubleClicked(); |
| | } |
| |
|
| | QGraphicsScene::mouseDoubleClickEvent(event); |
| | } |
| |
|
| |
|
| | std::vector<Gui::DAG::Vertex> Model::getAllSelected() |
| | { |
| | std::vector<Gui::DAG::Vertex> 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); |
| |
|
| | |
| | QGraphicsItem* item = itemAt(event->scenePos(), QTransform()); |
| | auto pixmapItem = dynamic_cast<QGraphicsPixmapItem*>(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); |
| |
|
| | |
| | std::vector<Gui::DAG::Vertex> selections = getAllSelected(); |
| | if (selections.size() == 1) { |
| | contextMenu.addAction(renameAction); |
| | |
| | if (!rect->isEditing()) { |
| | const_cast<Gui::ViewProviderDocumentObject*>(record.VPDObject) |
| | ->setupContextMenu(&contextMenu, this, SLOT(editingStartSlot())); |
| | } |
| | else { |
| | contextMenu.addAction(editingFinishedAction); |
| | } |
| | } |
| |
|
| | if (!contextMenu.actions().isEmpty()) { |
| | contextMenu.exec(event->screenPos()); |
| | } |
| | } |
| |
|
| | QGraphicsScene::contextMenuEvent(event); |
| | } |
| |
|
| | void Model::onRenameSlot() |
| | { |
| | assert(!proxy); |
| | std::vector<Gui::DAG::Vertex> selections = getAllSelected(); |
| | assert(selections.size() == 1); |
| |
|
| | auto lineEdit = new LineEdit(); |
| | auto text = (*theGraph)[selections.front()].text.get(); |
| | lineEdit->setText(text->toPlainText()); |
| | connect(lineEdit, &LineEdit::acceptedSignal, this, &Model::renameAcceptedSlot); |
| | connect(lineEdit, &LineEdit::rejectedSignal, this, &Model::renameRejectedSlot); |
| |
|
| | proxy = this->addWidget(lineEdit); |
| | proxy->setGeometry(text->sceneBoundingRect()); |
| |
|
| | lineEdit->selectAll(); |
| | QTimer::singleShot(0, lineEdit, qOverload<>(&QLineEdit::setFocus)); |
| | } |
| |
|
| | void Model::renameAcceptedSlot() |
| | { |
| | assert(proxy); |
| |
|
| | std::vector<Gui::DAG::Vertex> selections = getAllSelected(); |
| | assert(selections.size() == 1); |
| | const GraphLinkRecord& record = findRecord(selections.front(), *graphLink); |
| |
|
| | auto lineEdit = qobject_cast<LineEdit*>(proxy->widget()); |
| | assert(lineEdit); |
| | const_cast<App::DocumentObject*>(record.DObject) |
| | ->Label.setValue(lineEdit->text().toUtf8().constData()); |
| |
|
| | finishRename(); |
| | } |
| |
|
| | void Model::renameRejectedSlot() |
| | { |
| | finishRename(); |
| | } |
| |
|
| | void Model::finishRename() |
| | { |
| | assert(proxy); |
| | this->removeItem(proxy); |
| | proxy->deleteLater(); |
| | proxy = nullptr; |
| | this->invalidate(); |
| | } |
| |
|
| | void Model::editingStartSlot() |
| | { |
| | auto action = qobject_cast<QAction*>(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<ViewProviderDocumentObject*>(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<Base::Type> out; |
| | Base::Type type; |
| | type = Base::Type::fromName("App::DocumentObjectGroup"); |
| | if (!type.isBad()) { |
| | out.push_back(type); |
| | } |
| | type = Base::Type::fromName("App::Part"); |
| | if (!type.isBad()) { |
| | out.push_back(type); |
| | } |
| | type = Base::Type::fromName("PartDesign::Body"); |
| | if (!type.isBad()) { |
| | out.push_back(type); |
| | } |
| |
|
| | return out; |
| | }; |
| |
|
| | auto testSkipType = [](const App::DocumentObject* dObject, const std::vector<Base::Type>& types) { |
| | for (const auto& currentType : 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)); |
| |
|
| | |
| | static std::vector<Base::Type> skipTypes = buildSkipTypes(); |
| | for (const auto& currentVertex : connectedVertices) { |
| | const GraphLinkRecord& record = findRecord(currentVertex, *graphLink); |
| | if (testSkipType(record.DObject, skipTypes)) { |
| | continue; |
| | } |
| | const_cast<ViewProviderDocumentObject*>(record.VPDObject)->hide(); |
| | } |
| |
|
| | const GraphLinkRecord& sourceRecord = findRecord(sourceIn, *graphLink); |
| | if (!testSkipType(sourceRecord.DObject, skipTypes)) { |
| | const_cast<ViewProviderDocumentObject*>(sourceRecord.VPDObject)->show(); |
| | } |
| | } |
| |
|
| |
|
| | #include <moc_DAGModel.cpp> |
| |
|