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 "partpolygon.h"
19 #include "QPropertyUndoCommand/qpropertyundocommand.h"
20 #include "elementscene.h"
21 #include "QetGraphicsItemModeler/qetgraphicshandleritem.h"
22 #include "qetelementeditor.h"
23 #include "qeticons.h"
24 #include "QetGraphicsItemModeler/qetgraphicshandlerutility.h"
25 
26 
27 /**
28  * @brief PartPolygon::PartPolygon
29  * Constructor
30  * @param editor : editor of this item
31  * @param parent : parent item
32  */
PartPolygon(QETElementEditor * editor,QGraphicsItem * parent)33 PartPolygon::PartPolygon(QETElementEditor *editor, QGraphicsItem *parent) :
34 	CustomElementGraphicPart(editor, parent),
35 	m_closed(false),
36 	m_undo_command(nullptr)
37 {
38 	m_insert_point = new QAction(tr("Ajouter un point"), this);
39 	m_insert_point->setIcon(QET::Icons::Add);
40 	connect(m_insert_point, &QAction::triggered, this, &PartPolygon::insertPoint);
41 	m_remove_point = new QAction(tr("Supprimer ce point"), this);
42 	m_remove_point->setIcon(QET::Icons::Remove);
43 	connect(m_remove_point, &QAction::triggered, this, &PartPolygon::removePoint);
44 }
45 
46 /**
47  * @brief PartPolygon::~PartPolygon
48  */
~PartPolygon()49 PartPolygon::~PartPolygon()
50 {
51 	if(m_undo_command) delete m_undo_command;
52 	removeHandler();
53 }
54 
55 /**
56  * @brief PartPolygon::paint
57  * Draw this polygon
58  * @param painter
59  * @param options
60  * @param widget
61  */
paint(QPainter * painter,const QStyleOptionGraphicsItem * options,QWidget * widget)62 void PartPolygon::paint(QPainter *painter, const QStyleOptionGraphicsItem *options, QWidget *widget)
63 {
64 	Q_UNUSED(widget);
65 
66 	applyStylesToQPainter(*painter);
67 
68 	QPen t = painter -> pen();
69 	t.setCosmetic(options && options -> levelOfDetail < 1.0);
70 	if (isSelected()) t.setColor(Qt::red);
71 	painter -> setPen(t);
72 
73 	m_closed ? painter -> drawPolygon (m_polygon) :
74 			   painter -> drawPolyline(m_polygon);
75 
76 	if (m_hovered)
77 		drawShadowShape(painter);
78 }
79 
80 /**
81  * @brief PartPolygon::fromXml
82  * Import the properties of this polygon from a xml element
83  * @param qde : Xml document to use
84  */
fromXml(const QDomElement & qde)85 void PartPolygon::fromXml(const QDomElement &qde)
86 {
87 	stylesFromXml(qde);
88 
89 	int i = 1;
90 	while(true)
91 	{
92 		if (QET::attributeIsAReal(qde, QString("x%1").arg(i)) &&\
93 			QET::attributeIsAReal(qde, QString("y%1").arg(i)))
94 			++ i;
95 
96 		else break;
97 	}
98 
99 	QPolygonF temp_polygon;
100 	for (int j = 1 ; j < i ; ++ j)
101 	{
102 		temp_polygon << QPointF(qde.attribute(QString("x%1").arg(j)).toDouble(),
103 								qde.attribute(QString("y%1").arg(j)).toDouble());
104 	}
105 	m_polygon = temp_polygon;
106 
107 	m_closed = qde.attribute("closed") != "false";
108 }
109 
110 /**
111  * @brief PartPolygon::toXml
112  * Export this polygin in xml
113  * @param xml_document : Xml document to use for create the xml element
114  * @return an xml element that describe this polygon
115  */
toXml(QDomDocument & xml_document) const116 const QDomElement PartPolygon::toXml(QDomDocument &xml_document) const
117 {
118 	QDomElement xml_element = xml_document.createElement("polygon");
119 	int i = 1;
120 	foreach(QPointF point, m_polygon) {
121 		point = mapToScene(point);
122 		xml_element.setAttribute(QString("x%1").arg(i), QString("%1").arg(point.x()));
123 		xml_element.setAttribute(QString("y%1").arg(i), QString("%1").arg(point.y()));
124 		++ i;
125 	}
126 	if (!m_closed) xml_element.setAttribute("closed", "false");
127 	stylesToXml(xml_element);
128 	return(xml_element);
129 }
130 
131 /**
132  * @brief PartPolygon::isUseless
133  * @return true if this part is irrelevant and does not deserve to be Retained / registered.
134  * A polygon is relevant when he have 2 differents points
135  */
isUseless() const136 bool PartPolygon::isUseless() const
137 {
138 	if (m_polygon.count() < 2) return(true);
139 
140 	for (int i = 1 ; i < m_polygon.count() ; ++ i)
141 		if (m_polygon[i] != m_polygon[i-1]) return(false);
142 
143 	return(true);
144 }
145 
146 /**
147  * @brief PartPolygon::sceneGeometricRect
148  * @return the minimum, margin-less rectangle this part can fit into, in scene
149  * coordinates. It is different from boundingRect() because it is not supposed
150  * to imply any margin, and it is different from shape because it is a regular
151  * rectangle, not a complex shape.
152  */
sceneGeometricRect() const153 QRectF PartPolygon::sceneGeometricRect() const {
154 	return(mapToScene(m_polygon.boundingRect()).boundingRect());
155 }
156 
157 /**
158  * @brief PartPolygon::startUserTransformation
159  * Start the user-induced transformation, provided this primitive is contained
160  * within the initial_selection_rect bounding rectangle.
161  * @param initial_selection_rect
162  */
startUserTransformation(const QRectF & initial_selection_rect)163 void PartPolygon::startUserTransformation(const QRectF &initial_selection_rect)
164 {
165 	Q_UNUSED(initial_selection_rect)
166 	saved_points_ = mapToScene(m_polygon).toList();
167 }
168 
169 /**
170  * @brief PartPolygon::handleUserTransformation
171  * Handle the user-induced transformation from initial_selection_rect to new_selection_rect
172  * @param initial_selection_rect
173  * @param new_selection_rect
174  */
handleUserTransformation(const QRectF & initial_selection_rect,const QRectF & new_selection_rect)175 void PartPolygon::handleUserTransformation(const QRectF &initial_selection_rect, const QRectF &new_selection_rect)
176 {
177 	QList<QPointF> mapped_points = mapPoints(initial_selection_rect, new_selection_rect, saved_points_);
178 	m_polygon = (mapFromScene(QPolygonF(mapped_points.toVector())));
179 }
180 
181 /**
182  * @brief PartPolygon::preferredScalingMethod
183  * This method is called by the decorator when it needs to determine the best
184  * way to interactively scale a primitive. It is typically called when only a
185  * single primitive is being scaled.
186  * @return : This reimplementation systematically returns QET::RoundScaleRatios.
187  */
preferredScalingMethod() const188 QET::ScalingMethod PartPolygon::preferredScalingMethod() const {
189 	return(QET::RoundScaleRatios);
190 }
191 
192 /**
193  * @brief PartPolygon::polygon
194  * @return the item's polygon, or an empty polygon if no polygon has been set.
195  */
polygon() const196 QPolygonF PartPolygon::polygon() const {
197 	return m_polygon;
198 }
199 
200 /**
201  * @brief PartPolygon::setPolygon
202  * Sets the item's polygon to be the given polygon.
203  * @param polygon
204  */
setPolygon(const QPolygonF & polygon)205 void PartPolygon::setPolygon(const QPolygonF &polygon)
206 {
207 	if (m_polygon == polygon) return;
208 	prepareGeometryChange();
209 	m_polygon = polygon;
210 	adjusteHandlerPos();
211 	emit polygonChanged();
212 }
213 
214 /**
215  * @brief PartPolygon::addPoint
216  * Add new point to polygon
217  * @param point
218  */
addPoint(const QPointF & point)219 void PartPolygon::addPoint(const QPointF &point)
220 {
221 	prepareGeometryChange();
222 	m_polygon << point;
223 }
224 
225 /**
226  * @brief PartPolygon::setLastPoint
227  * Set the last point of polygon to @point
228  * @param point
229  */
setLastPoint(const QPointF & point)230 void PartPolygon::setLastPoint(const QPointF &point)
231 {
232 	if (m_polygon.size())
233 		m_polygon.pop_back();
234 
235 	prepareGeometryChange();
236 	m_polygon << point;
237 }
238 
239 /**
240  * @brief PartPolygon::removeLastPoint
241  * Remove the last point of polygon
242  */
removeLastPoint()243 void PartPolygon::removeLastPoint()
244 {
245 	if (m_polygon.size())
246 	{
247 		prepareGeometryChange();
248 		m_polygon.pop_back();
249 	}
250 }
251 
setClosed(bool close)252 void PartPolygon::setClosed(bool close)
253 {
254 	if (m_closed == close) return;
255 	prepareGeometryChange();
256 	m_closed = close;
257 	emit closedChange();
258 }
259 
260 /**
261  * @brief PartPolygon::itemChange
262  * @param change
263  * @param value
264  * @return
265  */
itemChange(QGraphicsItem::GraphicsItemChange change,const QVariant & value)266 QVariant PartPolygon::itemChange(QGraphicsItem::GraphicsItemChange change, const QVariant &value)
267 {
268 	if (change == ItemSelectedHasChanged && scene())
269 	{
270 		if (value.toBool() == true)
271 		{
272 				//When item is selected, he must to be up to date whene the selection in the scene change, for display or not the handler,
273 				//according to the number of selected items.
274 			connect(scene(), &QGraphicsScene::selectionChanged, this, &PartPolygon::sceneSelectionChanged);
275 
276 			if (scene()->selectedItems().size() == 1)
277 				addHandler();
278 		}
279 		else
280 		{
281 			disconnect(scene(), &QGraphicsScene::selectionChanged, this, &PartPolygon::sceneSelectionChanged);
282 			removeHandler();
283 		}
284 	}
285 	else if (change == ItemPositionHasChanged)
286 	{
287 		adjusteHandlerPos();
288 	}
289 	else if (change == ItemSceneChange)
290 	{
291 		if(scene())
292 			disconnect(scene(), &QGraphicsScene::selectionChanged, this, &PartPolygon::sceneSelectionChanged);
293 
294 		setSelected(false); //This is item removed from scene, then we deselect this, and so, the handlers is also removed.
295 	}
296 
297 	return QGraphicsItem::itemChange(change, value);
298 }
299 
300 /**
301  * @brief PartPolygon::sceneEventFilter
302  * @param watched
303  * @param event
304  * @return
305  */
sceneEventFilter(QGraphicsItem * watched,QEvent * event)306 bool PartPolygon::sceneEventFilter(QGraphicsItem *watched, QEvent *event)
307 {
308 		//Watched must be an handler
309 	if(watched->type() == QetGraphicsHandlerItem::Type)
310 	{
311 		QetGraphicsHandlerItem *qghi = qgraphicsitem_cast<QetGraphicsHandlerItem *>(watched);
312 
313 		if(m_handler_vector.contains(qghi)) //Handler must be in m_vector_index, then we can start resize
314 		{
315 			m_vector_index = m_handler_vector.indexOf(qghi);
316 			if (m_vector_index != -1)
317 			{
318 				if(event->type() == QEvent::GraphicsSceneMousePress) //Click
319 				{
320 					handlerMousePressEvent(qghi, static_cast<QGraphicsSceneMouseEvent *>(event));
321 					return true;
322 				}
323 				else if(event->type() == QEvent::GraphicsSceneMouseMove) //Move
324 				{
325 					handlerMouseMoveEvent(qghi, static_cast<QGraphicsSceneMouseEvent *>(event));
326 					return true;
327 				}
328 				else if (event->type() == QEvent::GraphicsSceneMouseRelease) //Release
329 				{
330 					handlerMouseReleaseEvent(qghi, static_cast<QGraphicsSceneMouseEvent *>(event));
331 					return true;
332 				}
333 			}
334 		}
335 	}
336 
337 	return false;
338 }
339 
contextMenuEvent(QGraphicsSceneContextMenuEvent * event)340 void PartPolygon::contextMenuEvent(QGraphicsSceneContextMenuEvent *event)
341 {
342 	m_context_menu_pos = event->pos();
343 	event->ignore();
344 	if (isSelected() && elementScene() && (elementScene()->behavior() == ElementScene::Normal))
345 	{
346 		QList<QAction *> list;
347 		list << m_insert_point;
348 		if (m_handler_vector.count() > 2)
349 		{
350 			for (QetGraphicsHandlerItem *qghi : m_handler_vector)
351 			{
352 				if (qghi->contains(qghi->mapFromScene(event->scenePos())))
353 				{
354 					list << m_remove_point;
355 					break;
356 				}
357 			}
358 		}
359 		elementScene()->editor()->contextMenu(event->screenPos(), list);
360 		event->accept();
361 	}
362 }
363 
364 /**
365  * @brief PartPolygon::adjusteHandlerPos
366  */
adjusteHandlerPos()367 void PartPolygon::adjusteHandlerPos()
368 {
369 	if(m_handler_vector.isEmpty())
370 		return;
371 
372 	if (m_handler_vector.size() == m_polygon.size())
373 	{
374 		QVector <QPointF> points_vector = mapToScene(m_polygon);
375 		for (int i = 0 ; i < points_vector.size() ; ++i)
376 			m_handler_vector.at(i)->setPos(points_vector.at(i));
377 	}
378 	else
379 	{
380 		qDeleteAll(m_handler_vector);
381 		m_handler_vector.clear();
382 		addHandler();
383 	}
384 }
385 
386 /**
387  * @brief PartPolygon::handlerMousePressEvent
388  * @param qghi
389  * @param event
390  */
handlerMousePressEvent(QetGraphicsHandlerItem * qghi,QGraphicsSceneMouseEvent * event)391 void PartPolygon::handlerMousePressEvent(QetGraphicsHandlerItem *qghi, QGraphicsSceneMouseEvent *event)
392 {
393 	Q_UNUSED(qghi);
394 	Q_UNUSED(event);
395 
396 	m_undo_command = new QPropertyUndoCommand(this, "polygon", QVariant(m_polygon));
397 	m_undo_command->setText(tr("Modifier un polygone"));
398 }
399 
400 /**
401  * @brief PartPolygon::handlerMouseMoveEvent
402  * @param qghi
403  * @param event
404  */
handlerMouseMoveEvent(QetGraphicsHandlerItem * qghi,QGraphicsSceneMouseEvent * event)405 void PartPolygon::handlerMouseMoveEvent(QetGraphicsHandlerItem *qghi, QGraphicsSceneMouseEvent *event)
406 {
407 	Q_UNUSED(qghi);
408 
409 	QPointF new_pos = event->scenePos();
410 	if (event->modifiers() != Qt::ControlModifier)
411 		new_pos = elementScene()->snapToGrid(event->scenePos());
412 	new_pos = mapFromScene(new_pos);
413 
414 	prepareGeometryChange();
415 	m_polygon.replace(m_vector_index, new_pos);
416 	adjusteHandlerPos();
417 	emit polygonChanged();
418 }
419 
420 /**
421  * @brief PartPolygon::handlerMouseReleaseEvent
422  * @param qghi
423  * @param event
424  */
handlerMouseReleaseEvent(QetGraphicsHandlerItem * qghi,QGraphicsSceneMouseEvent * event)425 void PartPolygon::handlerMouseReleaseEvent(QetGraphicsHandlerItem *qghi, QGraphicsSceneMouseEvent *event)
426 {
427 	Q_UNUSED(qghi);
428 	Q_UNUSED(event);
429 
430 	m_undo_command->setNewValue(QVariant(m_polygon));
431 	elementScene()->undoStack().push(m_undo_command);
432 	m_undo_command = nullptr;
433 	m_vector_index = -1;
434 }
435 
436 /**
437  * @brief PartPolygon::sceneSelectionChanged
438  * When the scene selection change, if there are several primitive selected, we remove the handler of this item
439  */
sceneSelectionChanged()440 void PartPolygon::sceneSelectionChanged()
441 {
442 	if (this->isSelected() && scene()->selectedItems().size() == 1)
443 		addHandler();
444 	else
445 		removeHandler();
446 }
447 
448 /**
449  * @brief PartPolygon::addHandler
450  * Add handlers for this item
451  */
addHandler()452 void PartPolygon::addHandler()
453 {
454 	if (m_handler_vector.isEmpty() && scene())
455 	{
456 		m_handler_vector = QetGraphicsHandlerItem::handlerForPoint(mapToScene(m_polygon));
457 
458 		for(QetGraphicsHandlerItem *handler : m_handler_vector)
459 		{
460 			handler->setColor(Qt::blue);
461 			scene()->addItem(handler);
462 			handler->installSceneEventFilter(this);
463 			handler->setZValue(this->zValue()+1);
464 		}
465 	}
466 }
467 
468 /**
469  * @brief PartPolygon::removeHandler
470  * Remove the handlers of this item
471  */
removeHandler()472 void PartPolygon::removeHandler()
473 {
474 	if (!m_handler_vector.isEmpty())
475 	{
476 		qDeleteAll(m_handler_vector);
477 		m_handler_vector.clear();
478 	}
479 }
480 
481 /**
482  * @brief PartPolygon::insertPoint
483  * Insert a point in this polygone
484  */
insertPoint()485 void PartPolygon::insertPoint()
486 {
487 	QPolygonF new_polygon = QetGraphicsHandlerUtility::polygonForInsertPoint(m_polygon, m_closed, elementScene()->snapToGrid(m_context_menu_pos));
488 
489 	if(new_polygon != m_polygon)
490 	{
491 			//Wrap the undo for avoid to merge the undo commands when user add several points.
492 		QUndoCommand *undo = new QUndoCommand(tr("Ajouter un point à un polygone"));
493 		new QPropertyUndoCommand(this, "polygon", m_polygon, new_polygon, undo);
494 		elementScene()->undoStack().push(undo);
495 	}
496 }
497 
498 /**
499  * @brief PartPolygon::removePoint
500  * remove a point on this polygon
501  */
removePoint()502 void PartPolygon::removePoint()
503 {
504 	if (m_handler_vector.size() == 2)
505 		return;
506 
507 	QPointF point = mapToScene(m_context_menu_pos);
508 	int index = -1;
509 	for (int i=0 ; i<m_handler_vector.size() ; i++)
510 	{
511 		QetGraphicsHandlerItem *qghi = m_handler_vector.at(i);
512 		if (qghi->contains(qghi->mapFromScene(point)))
513 		{
514 			index = i;
515 			break;
516 		}
517 	}
518 	if (index > -1 && index<m_handler_vector.count())
519 	{
520 		QPolygonF polygon = this->polygon();
521 		polygon.removeAt(index);
522 
523 			//Wrap the undo for avoid to merge the undo commands when user add several points.
524 		QUndoCommand *undo = new QUndoCommand(tr("Supprimer un point d'un polygone"));
525 		new QPropertyUndoCommand(this, "polygon", this->polygon(), polygon, undo);
526 		elementScene()->undoStack().push(undo);
527 	}
528 
529 }
530 
531 /**
532  * @brief PartPolygon::shape
533  * @return the shape of this item
534  */
shape() const535 QPainterPath PartPolygon::shape() const
536 {
537 	QPainterPath shape;
538 	shape.addPolygon(m_polygon);
539 
540 	if (m_closed)
541 		shape.lineTo(m_polygon.first());
542 
543 	QPainterPathStroker pps;
544 	pps.setWidth(m_hovered? penWeight()+SHADOWS_HEIGHT : penWeight());
545 	shape = pps.createStroke(shape);
546 
547 	return shape;
548 }
549 
shadowShape() const550 QPainterPath PartPolygon::shadowShape() const
551 {
552 	QPainterPath shape;
553 	shape.addPolygon(m_polygon);
554 
555 	if (m_closed)
556 		shape.lineTo(m_polygon.first());
557 
558 	QPainterPathStroker pps;
559 	pps.setWidth(penWeight());
560 
561 	return (pps.createStroke(shape));
562 }
563 
564 /**
565  * @brief PartPolygon::boundingRect
566  * @return the bounding rect of this item
567  */
boundingRect() const568 QRectF PartPolygon::boundingRect() const
569 {
570 	QRectF r = m_polygon.boundingRect();
571 
572 	qreal adjust = (SHADOWS_HEIGHT + penWeight()) / 2;
573 		//We add 0.5 because CustomElementGraphicPart::drawShadowShape
574 		//draw a shape bigger of 0.5 when pen weight is to 0.
575 	if (penWeight() == 0) adjust += 0.5;
576 
577 	r.adjust(-adjust, -adjust, adjust, adjust);
578 
579 	return(r);
580 }
581