/* Copyright 2006-2021 The QElectroTech Team This file is part of QElectroTech. QElectroTech is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 2 of the License, or (at your option) any later version. QElectroTech 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 General Public License for more details. You should have received a copy of the GNU General Public License along with QElectroTech. If not, see . */ #include "elementsmover.h" #include "conductorautonumerotation.h" #include "diagram.h" #include "qetgraphicsitem/conductor.h" #include "diagramcommands.h" #include "qetgraphicsitem/conductortextitem.h" #include "qetgraphicsitem/diagramimageitem.h" #include "qetgraphicsitem/dynamicelementtextitem.h" #include "qetgraphicsitem/element.h" #include "qetgraphicsitem/elementtextitemgroup.h" #include "qetgraphicsitem/independenttextitem.h" #include "undocommand/addgraphicsobjectcommand.h" #include "qetapp.h" #include "qetdiagrameditor.h" /** @brief ElementsMover::ElementsMover Constructor */ ElementsMover::ElementsMover(){} /** @brief ElementsMover::~ElementsMover Destructor */ ElementsMover::~ElementsMover(){} /** @brief ElementsMover::isReady @return True if this element mover is ready to be used. A element mover is ready when the previous managed movement is finish. */ bool ElementsMover::isReady() const { return(!m_movement_running); } /** @brief ElementsMover::beginMovement Start a new movement @param diagram diagram where the movement is applied @param driver_item item moved by mouse and don't be moved by Element mover @return the numbers of items to be moved or -1 if movement can't be init. */ int ElementsMover::beginMovement(Diagram *diagram, QGraphicsItem *driver_item) { // They must be no movement in progress if (m_movement_running) return(-1); // Be sure we have diagram to work if (!diagram) return(-1); m_diagram = diagram; if (!diagram->views().isEmpty()) { const auto qde = QETApp::diagramEditorAncestorOf(diagram->views().at(0)); if (qde) { m_status_bar = qde->statusBar(); } } else { m_status_bar.clear(); } // Take count of driver item m_movement_driver = driver_item; // At the beginning of movement, move is NULL m_current_movement -= m_current_movement; m_moved_content = DiagramContent(diagram); m_moved_content.removeNonMovableItems(); //Remove element text, if the parent element is selected. for(const auto &deti : qAsConst(m_moved_content.m_element_texts)) { if(m_moved_content.m_elements.contains(deti->parentElement())) { m_moved_content.m_element_texts.remove(deti); } } for(const auto &etig : qAsConst(m_moved_content.m_texts_groups)) { if (m_moved_content.m_elements.contains(etig->parentElement())) { m_moved_content.m_texts_groups.remove(etig); } } if (!m_moved_content.count()) return(-1); /* At this point, we've got all info to manage movement. * There is now a move in progress */ m_movement_running = true; return(m_moved_content.count()); } /** @brief ElementsMover::continueMovement Add a move to the current movement. @param movement movement to applied */ void ElementsMover::continueMovement(const QPointF &movement) { if (!m_movement_running || movement.isNull()) return; m_current_movement += movement; //Move every movable item, except conductor typedef DiagramContent dc; for (auto &qgi : m_moved_content.items(dc::Elements | dc::TextFields | dc::Images | dc::Shapes | dc::ElementTextFields | dc::TextGroup | dc::ConductorsToMove)) { if (qgi == m_movement_driver) continue; qgi->setPos(qgi->pos() + movement); } // Move some conductors for (auto &conductor : m_moved_content.m_conductors_to_update) { #if TODO_LIST #pragma message("@TODO fix this problem correctly, probably we must to see conductor class.") #endif //Due to a weird behavior, we must to ensure that the position of the conductor is to (0,0). //If not, in some unknown case the function QGraphicsScene::itemsBoundingRect() return a rectangle //that take in acount the pos() of the conductor, even if the bounding rect returned by the conductor is not in the pos(). //For the user this situation appear when the top right of the folio is not at the top right of the graphicsview, //but displaced to the right and/or bottom. //@TODO fix this problem correctly, probably we must to see conductor class. // if (c->pos() != QPointF(0,0)) { //<- they work, but the conductor text return to her original pos when the pos is set by user and not auto // c->setPos(0,0); // because set the pos to 0,0 so text move to, and after call updatePath but because text pos is user defined // } // we don't move it. conductor->updatePath(); } if (m_status_bar && m_movement_driver) { const auto point_{m_movement_driver->scenePos()}; m_status_bar->showMessage(QString("x %1 : y %2").arg(QString::number(point_.x()), QString::number(point_.y()))); } } /** @brief ElementsMover::endMovement Ended the current movement by creating an undo added to the undostack of the diagram. If there is only one element moved, we try to auto-connect new conductor from this element and other possible element. */ void ElementsMover::endMovement() { // A movement must be inited if (!m_movement_running) return; //empty command to be used has parent of commands below QUndoCommand *undo_object{new QUndoCommand()}; //Create undo move if there is a movement if (!m_current_movement.isNull()) { QUndoCommand *quc{new MoveElementsCommand(m_diagram, m_moved_content, m_current_movement, undo_object)}; undo_object->setText(quc->text()); } //There is only one element moved, and project authorize auto conductor, //we try auto connection of conductor; typedef DiagramContent dc; if (m_moved_content.items(dc::TextFields | dc::Images | dc::Shapes).isEmpty() && m_moved_content.items(dc::Elements).size() == 1 && m_diagram->project()->autoConductor()) { const Element *elmt{m_moved_content.m_elements.first()}; const auto aligned_free_terminals{elmt->AlignedFreeTerminals()}; if (const int acc = aligned_free_terminals.size()) { for (const auto &pair : aligned_free_terminals) { Conductor *conductor{new Conductor(pair.first, pair.second)}; //Create an undo object for each new auto conductor, with undo_object for parent new AddGraphicsObjectCommand(conductor, m_diagram, QPointF(), undo_object); if (undo_object->text().isEmpty()) undo_object->setText(QObject::tr("Ajouter %n conducteur(s)", "add a numbers of conductor one or more", acc)); //Get all conductors at the same potential of conductor const auto conductors_list{conductor->relatedPotentialConductors()}; //Compare the properties of every conductors stored in conductors_list, //if every conductors properties is equal, we use this properties for conductor. ConductorProperties others_properties; bool use_properties = false; if (!conductors_list.isEmpty()) { use_properties = true; others_properties = (*conductors_list.cbegin())->properties(); for (const auto &cond : conductors_list) if (cond->properties() != others_properties) use_properties = false; } if (use_properties) conductor->setProperties(others_properties); else { conductor -> setProperties(m_diagram -> defaultConductorProperties); //Autonum the new conductor, the undo command associated for this, have for parent undo_object ConductorAutoNumerotation can (conductor, m_diagram, undo_object); can.numerate(); } } } } //Add undo_object if have child if (undo_object->childCount()) m_diagram->undoStack().push(undo_object); else delete undo_object; // There is no movement in progress now m_movement_running = false; m_moved_content.clear(); if (m_status_bar) { m_status_bar->clearMessage(); } }