/*************************************************************************** * 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 #endif #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); } ViewEntryRectItem::ViewEntryRectItem(QGraphicsItem* parent) : QGraphicsRectItem(parent) { selected = false; preSelected = false; editing = 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; if (editing) brush = editBrush; //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()), visibleIcon(new QGraphicsPixmapItem()), stateIcon(new QGraphicsPixmapItem()), icon(new QGraphicsPixmapItem()), text(new QGraphicsTextItem()), row(0), column(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->visibleIcon->setZValue(0.0); this->stateIcon->setZValue(0.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; 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); 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::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; iconToIcon = iconSize * 0.25; iconToText = iconSize / 2.0; 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].visibleIcon.get()); this->addItem((*theGraph)[virginVertex].stateIcon.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); //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)); rectangle->setEditingBrush(QBrush(Qt::yellow)); (*theGraph)[virginVertex].icon->setPixmap(VPDObjectIn.getIcon().pixmap(iconSize, iconSize)); (*theGraph)[virginVertex].stateIcon->setPixmap(passPixmap); 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].visibleIcon.get()); this->removeItem((*theGraph)[vertex].stateIcon.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::slotInEdit(const ViewProviderDocumentObject& VPDObjectIn) { ViewEntryRectItem *rect = (*theGraph)[findRecord(&VPDObjectIn).vertex].rectangle.get(); rect->editingStart(); this->invalidate(); } void Model::slotResetEdit(const ViewProviderDocumentObject& VPDObjectIn) { ViewEntryRectItem *rect = (*theGraph)[findRecord(&VPDObjectIn).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. //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(); } updateStates(); } 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) { 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) { (*theGraph)[edge].connector = std::shared_ptr(new QGraphicsPathItem()); (*theGraph)[edge].connector->setZValue(0.0); this->addItem((*theGraph)[edge].connector.get()); } } } indexVerticesEdges(); Path sorted; boost::topological_sort(*theGraph, std::back_inserter(sorted)); //index the vertices in sort order. int tempIndex = 0; for (const auto ¤tVertex : sorted) { (*theGraph)[currentVertex].topoSortIndex = tempIndex; tempIndex++; } 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) { // std::cout << std::endl << std::endl; 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) { 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) columnMask |= (*theGraph)[*it].column; farthestParentIndex = currentParentIndex; } } // std::cout << "mask for " << findRecord(currentVertex).DObject->Label.getValue() << " " << // columnMask.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. int destinationColumn = maxColumn + 1; //default to new column for (const auto ¤tParent : parentVertices) { if (((*theGraph)[currentParent].column & columnMask).none()) { //go with first parent for now. destinationColumn = static_cast(std::log2((*theGraph)[currentParent].column.to_ulong())); 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); auto *visiblePixmap = (*theGraph)[currentVertex].visibleIcon.get(); visiblePixmap->setTransform(QTransform::fromTranslate(0.0, rowHeight * currentRow + rowHeight)); //calculate x location later. auto *statePixmap = (*theGraph)[currentVertex].stateIcon.get(); statePixmap->setTransform(QTransform::fromTranslate(0.0, rowHeight * currentRow + rowHeight)); //calculate x location later. 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())); text->setDefaultTextColor(currentBrush.color()); 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.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); float dependentX = pointSpacing * static_cast(std::log2((*theGraph)[target].column.to_ulong())) - 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 == static_cast(std::log2((*theGraph)[target].column.to_ulong()))) 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); float yPosition = dependentY - 2.0 * radius; 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); } else //radius to the left. { QRectF arcRect(currentX - 2.0 * radius, yPosition, width, height); path.arcTo(arcRect, 0.0, -90.0); } path.lineTo(dependentX, dependentY); } pathItem->setPath(path); } currentRow++; } float columnSpacing = (maxColumn * pointSpacing); for (const auto ¤tVertex : sorted) { float currentX = columnSpacing; currentX += pointToIcon; auto *visiblePixmap = (*theGraph)[currentVertex].visibleIcon.get(); QTransform visibleIconTransform = QTransform::fromTranslate(currentX, 0.0); visiblePixmap->setTransform(visiblePixmap->transform() * visibleIconTransform); currentX += iconSize + iconToIcon; auto *statePixmap = (*theGraph)[currentVertex].stateIcon.get(); QTransform stateIconTransform = QTransform::fromTranslate(currentX, 0.0); statePixmap->setTransform(statePixmap->transform() * stateIconTransform); currentX += iconSize + iconToIcon; auto *pixmap = (*theGraph)[currentVertex].icon.get(); QTransform iconTransform = QTransform::fromTranslate(currentX, 0.0); pixmap->setTransform(pixmap->transform() * iconTransform); currentX += iconSize + iconToText; auto *text = (*theGraph)[currentVertex].text.get(); QTransform textTransform = QTransform::fromTranslate(currentX, 0.0); text->setTransform(text->transform() * textTransform); auto *rectangle = (*theGraph)[currentVertex].rectangle.get(); QRectF rect = rectangle->rect(); rect.setWidth(currentX + 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::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_T(currentVertex, *theGraph, Graph) { const GraphLinkRecord &record = findRecord(currentVertex); 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 = (record.DObject->isError()) ? FeatureState::Fail : FeatureState::Pass; if (currentFeatureState != (*theGraph)[currentVertex].lastFeatureState) { if (currentFeatureState == FeatureState::Pass) { (*theGraph)[currentVertex].stateIcon->setPixmap(passPixmap); (*theGraph)[currentVertex].stateIcon->setToolTip(QString()); } else { (*theGraph)[currentVertex].stateIcon->setPixmap(failPixmap); (*theGraph)[currentVertex].stateIcon->setToolTip(QString::fromAscii(record.DObject->getStatusString())); } (*theGraph)[currentVertex].lastFeatureState = currentFeatureState; } } } 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) { 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) { ViewEntryRectItem *rect = dynamic_cast(*currentItem); if (!rect) continue; const GraphLinkRecord &selectionRecord = findRecord(rect); Gui::Selection().addSelection(selectionRecord.DObject->getDocument()->getName(), selectionRecord.DObject->getNameInDocument()); } }; auto toggleSelect = [](const App::DocumentObject *dObjectIn, ViewEntryRectItem *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) { ViewEntryRectItem *rect = getRectFromPosition(event->scenePos()); if (rect) { const GraphLinkRecord &record = findRecord(rect); //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) { auto selections = getAllSelected(); assert(selections.size() == 1); const GraphLinkRecord &record = findRecord(selections.front()); 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_T(currentVertex, *theGraph, Graph) { if ((*theGraph)[currentVertex].rectangle->isSelected()) out.push_back(currentVertex); } return out; } void Model::contextMenuEvent(QGraphicsSceneContextMenuEvent* event) { ViewEntryRectItem *rect = getRectFromPosition(event->scenePos()); if (rect) { const GraphLinkRecord &record = findRecord(rect); 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() { // std::cout << std::endl << "inside rename slot" << std::endl << std::endl; 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()); 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()); 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()); Gui::Document* doc = Gui::Application::Instance->getDocument(record.DObject->getDocument()); doc->commitCommand(); doc->resetEdit(); doc->getDocument()->recompute(); } #include