mirror of
https://github.com/qelectrotech/qelectrotech-source-mirror.git
synced 2026-09-29 22:24:13 +02:00
2dbaa69186
Grouping two symbols and then locking one of them (Lock position in its properties) left the group in a broken state: dragging the unlocked symbol pulled it away while the locked one stayed, and dragging the locked one did nothing. The move simply dropped locked items, so the rest of the group went without them. A group with a locked member now does not move at all, whichever member is dragged, and the status bar says why. This is the rule the item-groups proposal (discussion #1070) set out for this case. The same rule applies to the arrow keys and to the Align commands, which share DiagramContent::removeNonMovableItems(). Also fixed on the way, for a plain selection with a locked symbol: a wire between the locked symbol and one being dragged kept its user-placed text moving with the dragged end. Such a wire is now redrawn only, as a wire to an unselected symbol already is. Checked in the GUI on two symbols joined by a wire (grafcet example), master against this branch, positions read from the saved file: - drag the unlocked member: master moves it 190 px, this branch moves nothing and shows the message - arrow keys on the selected group (3 runs each): master moves the unlocked member, this branch nothing - the same two symbols ungrouped: both move the unlocked one, as before - user-placed wire text: master shifts it 190 px, this branch keeps it ctest: 34/34. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
288 lines
9.0 KiB
C++
288 lines
9.0 KiB
C++
/*
|
|
Copyright 2006-2026 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 "qetproject.h"
|
|
#include "autobreakconductor.h"
|
|
#include "conductorautonumerotation.h"
|
|
#include "diagram.h"
|
|
#include "itemgroups.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)
|
|
{
|
|
m_driver_held = false;
|
|
|
|
// 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();
|
|
|
|
//A grouped driver left out of the move belongs to a group that one
|
|
//locked member holds in place: it stays with the group
|
|
m_driver_held = driver_item
|
|
&& !ItemGroups::groupOf(driver_item).isNull()
|
|
&& !m_moved_content.items().contains(driver_item);
|
|
if (m_driver_held && m_status_bar) {
|
|
m_status_bar->showMessage(QObject::tr(
|
|
"Ce groupe ne peut pas être déplacé : "
|
|
"la position d'un de ses éléments est verrouillée."));
|
|
}
|
|
|
|
//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::holds
|
|
@return true if @a item is the item the user drags and it must not move:
|
|
it is in a group that a locked member keeps in place (#1146). Each item
|
|
that drives a movement asks before moving itself.
|
|
*/
|
|
bool ElementsMover::holds(const QGraphicsItem *item) const
|
|
{
|
|
return m_driver_held && item && item == m_movement_driver;
|
|
}
|
|
|
|
/**
|
|
@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());
|
|
}
|
|
|
|
//Auto-break conductors: for each moved element, break any conductor
|
|
//whose path passes through a terminal dock point, and reconnect through
|
|
//the element. The element is already at its final position on screen.
|
|
if (m_diagram->project()->autoBreakConductor())
|
|
{
|
|
QList<Conductor *> conductors_handled;
|
|
QSet<Terminal *> used_terminals;
|
|
for (Element *e : m_moved_content.m_elements) {
|
|
autoBreakConductors(m_diagram, e, undo_object,
|
|
conductors_handled, used_terminals);
|
|
}
|
|
}
|
|
|
|
//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();
|
|
|
|
//Keep saying why a held group did not move
|
|
if (m_status_bar && !m_driver_held) {
|
|
m_status_bar->clearMessage();
|
|
}
|
|
}
|