1 /*
2 	Copyright 2006-2019 The QElectroTech Team
3 	This file is part of QElectroTech.
4 
5 	QElectroTech is free software: you can redistribute it and/or modify
6 	it under the terms of the GNU General Public License as published by
7 	the Free Software Foundation, either version 2 of the License, or
8 	(at your option) any later version.
9 
10 	QElectroTech is distributed in the hope that it will be useful,
11 	but WITHOUT ANY WARRANTY; without even the implied warranty of
12 	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 	GNU General Public License for more details.
14 
15 	You should have received a copy of the GNU General Public License
16 	along with QElectroTech.  If not, see <http://www.gnu.org/licenses/>.
17 */
18 #include "elementsmover.h"
19 #include "conductor.h"
20 #include "conductortextitem.h"
21 #include "diagram.h"
22 #include "diagramcommands.h"
23 #include "element.h"
24 #include "independenttextitem.h"
25 #include "diagramimageitem.h"
26 #include "conductorautonumerotation.h"
27 #include "dynamicelementtextitem.h"
28 #include "elementtextitemgroup.h"
29 
30 /**
31  * @brief ElementsMover::ElementsMover Constructor
32  */
ElementsMover()33 ElementsMover::ElementsMover() :
34 	movement_running_(false),
35 	current_movement_(),
36 	diagram_(nullptr),
37 	m_movement_driver(nullptr),
38 	m_moved_content()
39 {
40 
41 }
42 
43 /**
44  * @brief ElementsMover::~ElementsMover Destructor
45  */
~ElementsMover()46 ElementsMover::~ElementsMover() {
47 }
48 
49 /**
50  * @brief ElementsMover::isReady
51  * @return True if this element mover is ready to be used.
52  * A element mover is ready when the previous managed movement is finish.
53  */
isReady() const54 bool ElementsMover::isReady() const {
55 	return(!movement_running_);
56 }
57 
58 /**
59  * @brief ElementsMover::beginMovement
60  * Start a new movement
61  * @param diagram diagram where the movement is applied
62  * @param driver_item item moved by mouse and don't be moved by Element mover
63  * @return the numbers of items to be moved or -1 if movement can't be init.
64  */
beginMovement(Diagram * diagram,QGraphicsItem * driver_item)65 int ElementsMover::beginMovement(Diagram *diagram, QGraphicsItem *driver_item)
66 {
67 		// They must be no movement in progress
68 	if (movement_running_) return(-1);
69 
70 		// Be sure we have diagram to work
71 	if (!diagram) return(-1);
72 	diagram_ = diagram;
73 
74 		// Take count of driver item
75 	m_movement_driver = driver_item;
76 
77 		// At the beginning of movement, move is NULL
78 	current_movement_ = QPointF(0.0, 0.0);
79 
80 	m_moved_content = DiagramContent(diagram);
81 	m_moved_content.removeNonMovableItems();
82 
83 		//Remove element text, if the parent element is selected.
84 	QList<DynamicElementTextItem *> deti_list = m_moved_content.m_element_texts.toList();
85 	for(DynamicElementTextItem *deti : deti_list) {
86 		if(m_moved_content.m_elements.contains(deti->parentElement())) {
87 			m_moved_content.m_element_texts.remove(deti);
88 		}
89 	}
90 
91 	QList<ElementTextItemGroup *> etig_list = m_moved_content.m_texts_groups.toList();
92 	for(ElementTextItemGroup *etig : etig_list) {
93 		if (m_moved_content.m_elements.contains(etig->parentElement())) {
94 			m_moved_content.m_texts_groups.remove(etig);
95 		}
96 	}
97 
98 	if (!m_moved_content.count()) return(-1);
99 
100 	/* At this point, we've got all info to manage movement.
101 	 * There is now a move in progress */
102 	movement_running_ = true;
103 
104 	return(m_moved_content.count());
105 }
106 
107 /**
108  * @brief ElementsMover::continueMovement
109  * Add a move to the current movement.
110  * @param movement movement to applied
111  */
continueMovement(const QPointF & movement)112 void ElementsMover::continueMovement(const QPointF &movement) {
113 	if (!movement_running_ || movement.isNull()) return;
114 
115 	current_movement_ += movement;
116 
117 	//Move every movable item, except conductor
118 	typedef DiagramContent dc;
119 	for (QGraphicsItem *qgi : m_moved_content.items(dc::Elements | dc::TextFields | dc::Images | dc::Shapes | dc::ElementTextFields | dc::TextGroup))
120 	{
121 		if (qgi == m_movement_driver)
122 			continue;
123 		qgi -> setPos(qgi->pos() + movement);
124 	}
125 
126 	// Move some conductors
127 	QList<Conductor *> c_list;
128 	c_list.append(m_moved_content.m_conductors_to_move);
129 	c_list.append(m_moved_content.m_conductors_to_update);
130 
131 	for (Conductor *c : c_list)
132 	{
133 			//Due to a weird behavior, we must to ensure that the position of the conductor is to (0,0).
134 			//If not, in some unknown case the function QGraphicsScene::itemsBoundingRect() return a rectangle
135 			//that take in acount the pos() of the conductor, even if the bounding rect returned by the conductor is not in the pos().
136 			//For the user this situation appear when the top right of the folio is not at the top right of the graphicsview,
137 			//but displaced to the right and/or bottom.
138 		if (c->pos() != QPointF(0,0)) {
139 			c->setPos(0,0);
140 		}
141 		c->updatePath();
142 	}
143 }
144 
145 /**
146  * @brief ElementsMover::endMovement
147  * Ended the current movement by creating an undo added to the undostack of the diagram.
148  * If there is only one element moved, we try to auto-connect new conductor from this element
149  * and other possible element.
150  */
endMovement()151 void ElementsMover::endMovement()
152 {
153 		// A movement must be inited
154 	if (!movement_running_) return;
155 
156 		//empty command to be used has parent of commands below
157 	QUndoCommand *undo_object = new QUndoCommand();
158 
159 		//Create undo move if there is a movement
160 	if (!current_movement_.isNull()) {
161 		QUndoCommand *quc = new MoveElementsCommand(diagram_, m_moved_content, current_movement_, undo_object);
162 		undo_object->setText(quc->text());
163 	}
164 
165 		//There is only one element moved, and project authorize auto conductor,
166 		//we try auto connection of conductor;
167 	typedef DiagramContent dc;
168 	if (m_moved_content.items(dc::TextFields | dc::Images | dc::Shapes).size() == 0 &&
169 		m_moved_content.items(dc::Elements).size() == 1 &&
170 		diagram_ -> project() -> autoConductor())
171 	{
172 		Element *elmt = m_moved_content.m_elements.first();
173 
174 		int acc = elmt->AlignedFreeTerminals().size();
175 
176 		while (!elmt -> AlignedFreeTerminals().isEmpty())
177 		{
178 			QPair <Terminal *, Terminal *> pair = elmt -> AlignedFreeTerminals().takeFirst();
179 
180 			Conductor *conductor = new Conductor(pair.first, pair.second);
181 
182 				//Create an undo object for each new auto conductor, with undo_object for parent
183 			new AddItemCommand<Conductor *>(conductor, diagram_, QPointF(), undo_object);
184 				if (undo_object->text().isEmpty())
185 					undo_object->setText(QObject::tr("Ajouter %n conducteur(s)", "add a numbers of conductor one or more", acc));
186 
187 				//Get all conductors at the same potential of conductor
188 			QSet <Conductor *> conductors_list = conductor->relatedPotentialConductors();
189 
190 				//Compare the properties of every conductors stored in conductors_list,
191 				//if every conductors properties is equal, we use this properties for conductor.
192 			ConductorProperties others_properties;
193 			bool use_properties = false;
194 			if (!conductors_list.isEmpty())
195 			{
196 				use_properties = true;
197 				others_properties = (*conductors_list.begin())->properties();
198 				foreach (Conductor *cond, conductors_list)
199 					if (cond->properties() != others_properties)
200 						use_properties = false;
201 			}
202 
203 			if (use_properties)
204 				conductor->setProperties(others_properties);
205 			else
206 			{
207 				conductor -> setProperties(diagram_ -> defaultConductorProperties);
208 					//Autonum the new conductor, the undo command associated for this, have for parent undo_object
209 				ConductorAutoNumerotation can  (conductor, diagram_, undo_object);
210 				can.numerate();
211 			}
212 		};
213 	}
214 
215 		//Add undo_object if have child
216 	if (undo_object->childCount() >= 1)
217 		diagram_ -> undoStack().push(undo_object);
218 	else
219 		delete undo_object;
220 
221 		// There is no movement in progress now
222 	movement_running_ = false;
223 	m_moved_content.clear();
224 }
225