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