achim 29c362fe4c Better handling of conductors when moving
For conductors, the setPos() function can result in negative
coordinates.
For unknown reasons, this can lead to an offset in the scene coordinate
system, resulting in a free space above and to the left of the drawing
frame. This free space could not be removed.

It is better to set the conductors using the conductor::updatePath()
function. If the conductor text has been moved by the user, the new
position of the text must be calculated.
It is important to position the elements first and then 'connect' the
conductors.
Setting the conductor position via setPos() was done in elemntsmover.cpp
(corrected here) and in Diagram::fromXML (corrected in the commit
'Better handling of conductors when creating from XML').
2025-08-14 22:03:05 +02:00

248 lines
7.6 KiB
C++

/*
Copyright 2006-2025 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 <http://www.gnu.org/licenses/>.
*/
#include "elementsmover.h"
#include "conductorautonumerotation.h"
#include "diagram.h"
#include "qetgraphicsitem/conductor.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"
#include "undocommand/movegraphicsitemcommand.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 and text group, if the parent element is selected.
const auto element_text{m_moved_content.m_element_texts};
for(const auto &deti : element_text) {
if(m_moved_content.m_elements.contains(deti->parentElement())) {
m_moved_content.m_element_texts.remove(deti);
}
}
const auto element_text_group{m_moved_content.m_texts_groups};
for(const auto &etig : element_text_group) {
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))
{
if (qgi == m_movement_driver)
continue;
qgi->setPos(qgi->pos() + movement);
}
QVector<Conductor *>list_conductors;
for(auto *con : m_moved_content.m_conductors_to_move){
list_conductors << con;
}
// update conductors 'conductors_to_move'
for(auto *cond : list_conductors){
cond->updatePath();
if(cond->textItem()->wasMovedByUser() == true)
cond->textItem()->setPos(cond->textItem()->pos()+movement);
}
// update conductors 'conductors_to_update'
for (auto &conductor : m_moved_content.m_conductors_to_update)
{
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 MoveGraphicsItemCommand(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
| dc::TerminalStrip).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();
}
}