QElectroTech  0.70
elementprimitivedecorator.cpp
Go to the documentation of this file.
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 */
19 #include "elementscene.h"
20 #include "customelementpart.h"
21 #include "editorcommands.h"
22 #include "qet.h"
23 #include <QPainter>
24 #include <QGraphicsSceneHoverEvent>
25 #include <QStyleOptionGraphicsItem>
26 #include <QGraphicsScene>
28 
34  QGraphicsObject(parent)
35 {
36  init();
37 }
38 
43 {
44  removeHandler();
45 }
46 
52  if (!decorated_items_.count() || !scene()) return(QRectF());
53 
54  //if @decorated_items_ contain one item and if this item is a vertical or horizontal partline, apply a specific methode
55  if ((decorated_items_.count() == 1) && (decorated_items_.first() -> xmlName() == "line")) {
56  QRectF horto = decorated_items_.first() -> sceneGeometricRect();
57  if (!horto.width() || !horto.height()) {
58  return (getSceneBoundingRect(decorated_items_.first() -> toItem()));
59  }
60  }
61  QRectF rect = decorated_items_.first() -> sceneGeometricRect();
62  foreach (CustomElementPart *item, decorated_items_) {
63  rect = rect.united(item -> sceneGeometricRect());
64  }
65  return(rect);
66 }
71 {
73 }
74 
84 void ElementPrimitiveDecorator::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
85 {
86  Q_UNUSED(option)
87  Q_UNUSED(widget)
88  painter -> save();
89 
90  // paint the original bounding rect
91  QPen pen(Qt::DashLine);
92  pen.setCosmetic(true);
93  painter -> setPen(pen);
94  painter -> drawRect(modified_bounding_rect_);
95 
96  // uncomment to draw the real bouding rect (=adjusted internal bounding rect)
97  // painter -> setBrush(QBrush(QColor(240, 0, 0, 127)));
98  // painter -> drawRect(boundingRect());
99  painter -> restore();
100 }
101 
105 void ElementPrimitiveDecorator::setItems(const QList<CustomElementPart *> &items)
106 {
108 
109  adjust();
110  show();
111  if (focusItem() != this) {
112  setFocus();
113  }
115 }
116 
120 void ElementPrimitiveDecorator::setItems(const QList<QGraphicsItem *> &items)
121 {
122  QList<CustomElementPart *> primitives;
123  for(QGraphicsItem *item : items)
124  {
125  if (CustomElementPart *part_item = dynamic_cast<CustomElementPart *>(item))
126  {
127  primitives << part_item;
128  }
129  }
130  setItems(primitives);
131 }
132 
136 QList<CustomElementPart *> ElementPrimitiveDecorator::items() const {
137  return(decorated_items_);
138 }
139 
143 QList<QGraphicsItem *> ElementPrimitiveDecorator::graphicsItems() const {
144  QList<QGraphicsItem *> list;
145  foreach (CustomElementPart *part_item, decorated_items_) {
146  if (QGraphicsItem *item = dynamic_cast<QGraphicsItem *>(part_item)) {
147  list << item;
148  }
149  }
150  return(list);
151 }
152 
158 {
162 }
163 
168 void ElementPrimitiveDecorator::mousePressEvent(QGraphicsSceneMouseEvent *event)
169 {
170  if (internalBoundingRect().contains(event->pos()))
171  {
173 
174  first_pos_ = decorated_items_.at(0) -> toItem() -> scenePos();
175  latest_pos_ = event -> scenePos();
176  mouse_offset_ = event -> scenePos() - first_pos_;
177  startMovement();
178  event->accept();
179  }
180  else
181  event->ignore();
182 }
183 
189 void ElementPrimitiveDecorator::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
190 {
191  QPointF scene_pos = event -> scenePos();
192  QPointF movement = scene_pos - latest_pos_;
193 
195  {
196  // When moving the selection, consider the position of the first selected item
197  QPointF current_position = scene_pos - mouse_offset_;
198  QPointF rounded_current_position = snapConstPointToGrid(current_position);
199  movement = rounded_current_position - decorated_items_.at(0) -> toItem() -> scenePos();
200 
201  QRectF bounding_rect = modified_bounding_rect_;
203  if (modified_bounding_rect_ != bounding_rect) {
205  }
206  latest_pos_ = event -> scenePos();
207  translateItems(movement);
208  }
209 
210 
211 
212 
213 }
214 
221 void ElementPrimitiveDecorator::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
222 {
223  Q_UNUSED(event)
224 
225  ElementEditionCommand *command = nullptr;
226 
228  {
229  QPointF movement = mapToScene(modified_bounding_rect_.topLeft()) - mapToScene(original_bounding_rect_.topLeft());
230  if (!movement.isNull())
231  {
232  MovePartsCommand *move_command = new MovePartsCommand(movement, nullptr, graphicsItems());
233  command = move_command;
234  }
235 
236  if (command) {
237  emit(actionFinished(command));
238  }
239 
240  adjust();
241  }
242 
244 }
245 
250 {
251  const qreal movement_length = 1.0;
252  QPointF movement;
253 
254  switch(e -> key())
255  {
256  case Qt::Key_Left: movement = QPointF(-movement_length, 0.0); break;
257  case Qt::Key_Right: movement = QPointF(+movement_length, 0.0); break;
258  case Qt::Key_Up: movement = QPointF(0.0, -movement_length); break;
259  case Qt::Key_Down: movement = QPointF(0.0, +movement_length); break;
260  }
261  if (!movement.isNull() && focusItem() == this)
262  {
263  if (!moving_by_keys_)
264  {
265  moving_by_keys_ = true;
266  keys_movement_ = movement;
267  }
268  else
269  {
270  keys_movement_ += movement;
271  }
272 
273  for(QGraphicsItem *qgi : graphicsItems())
274  {
275  qgi -> setPos(qgi -> pos() + movement);
276  adjust();
277  }
278 
279  e->accept();
280  return;
281  }
282 
283  QGraphicsObject::keyPressEvent(e);
284 }
285 
290  // detecte le relachement d'une touche de direction ( = deplacement de parties)
291  if(
292  (e -> key() == Qt::Key_Left || e -> key() == Qt::Key_Right ||\
293  e -> key() == Qt::Key_Up || e -> key() == Qt::Key_Down) &&\
294  moving_by_keys_ && !e -> isAutoRepeat()
295  ) {
296  // cree un objet d'annulation pour le mouvement qui vient de se finir
298  keys_movement_ = QPointF();
299  moving_by_keys_ = false;
300  e->accept();
301  return;
302  }
303  QGraphicsObject::keyPressEvent(e);
304 }
305 
310 {
311  setFlag(QGraphicsItem::ItemIsFocusable, true);
313  setAcceptHoverEvents(true);
314 }
315 
321 }
322 
328  prepareGeometryChange();
330  update();
332 }
333 
338  adjust();
339 
340  foreach(CustomElementPart *item, decorated_items_) {
341  item -> startUserTransformation(mapToScene(original_bounding_rect_).boundingRect());
342  }
343 }
344 
348 void ElementPrimitiveDecorator::applyMovementToRect(int movement_type, const QPointF &movement, QRectF &rect) {
349  qreal new_value;
350  QPointF new_point;
351 
352  switch (movement_type) {
353  case QET::MoveArea:
354  rect.translate(movement.x(), movement.y());
355  break;
357  new_point = rect.topLeft() + movement;
358  rect.setTopLeft(new_point);
359  break;
361  new_value = rect.top() + movement.y();
362  rect.setTop(new_value);
363  break;
365  new_point = rect.topRight() + movement;
366  rect.setTopRight(new_point);
367  break;
369  new_value = rect.left() + movement.x();
370  rect.setLeft(new_value);
371  break;
373  new_value = rect.right() + movement.x();
374  rect.setRight(new_value);
375  break;
377  new_point = rect.bottomLeft() + movement;
378  rect.setBottomLeft(new_point);
379  break;
381  new_value = rect.bottom() + movement.y();
382  rect.setBottom(new_value);
383  break;
385  new_point = rect.bottomRight() + movement;
386  rect.setBottomRight(new_point);
387  break;
388  }
389 }
390 
392  if (decorated_items_.count() == 1) {
393  return(decorated_items_.first());
394  }
395  return(nullptr);
396 }
397 
401 void ElementPrimitiveDecorator::translateItems(const QPointF &movement) {
402  if (!decorated_items_.count()) return;
403 
404  foreach(QGraphicsItem *qgi, graphicsItems()) {
405  // this is a naive, proof-of-concept implementation; we actually need to take
406  // the grid into account and create a command object in mouseReleaseEvent()
407  qgi -> moveBy(movement.x(), movement.y());
408  }
409 }
410 
411 
416 void ElementPrimitiveDecorator::scaleItems(const QRectF &original_rect, const QRectF &new_rect) {
417  if (!decorated_items_.count()) return;
418  if (original_rect == new_rect) return;
419  if (!original_rect.width() || !original_rect.height()) return; // cowardly flee division by zero FIXME?
420 
421  QRectF scene_original_rect = mapToScene(original_rect).boundingRect();
422  QRectF scene_new_rect = mapToScene(new_rect).boundingRect();
423 
424  foreach(CustomElementPart *item, decorated_items_) {
425  item -> handleUserTransformation(scene_original_rect, scene_new_rect);
426  }
427 }
428 
432 QRectF ElementPrimitiveDecorator::getSceneBoundingRect(QGraphicsItem *item) const {
433  if (!item) return(QRectF());
434  return(item -> mapRectToScene(item -> boundingRect()));
435 }
436 
438 {
439  QRectF primitive_rect = modified_bounding_rect_;
440  QVector <QPointF> vector;
441  QPointF half;
442 
443  vector << primitive_rect.topLeft(); //top left
444  half = primitive_rect.center();
445  half.setY(primitive_rect.top());
446  vector << half; //middle top
447  vector << primitive_rect.topRight(); //top right
448  half = primitive_rect.center();
449  half.setX(primitive_rect.left());
450  vector << half; //middle left
451  half = primitive_rect.center();
452  half.setX(primitive_rect.right());
453  vector << half; //middle right
454  vector << primitive_rect.bottomLeft(); //bottom left
455  half = primitive_rect.center();
456  half.setY(primitive_rect.bottom());
457  vector << half; //middle bottom
458  vector << primitive_rect.bottomRight(); //bottom right
459 
460  return vector;
461 }
462 
467 {
468  QVector <QPointF> points_vector = mapToScene(getResizingsPoints());
469  for (int i = 0 ; i < points_vector.size() ; ++i)
470  m_handler_vector.at(i)->setPos(points_vector.at(i));
471 }
472 
479 {
480  Q_UNUSED(event);
481 
482  QVector <QPointF> points = getResizingsPoints();
483 
485 
486  first_pos_ = latest_pos_ = mapToScene(points.at(current_operation_square_));
487  startMovement();
488 }
489 
496 {
497  Q_UNUSED(qghi);
498 
499  QPointF scene_pos = event -> scenePos();
500  QPointF movement = scene_pos - latest_pos_;
501 
502  // For convenience purposes, we may need to adjust mouse movements.
503  QET::ScalingMethod scaling_method = scalingMethod(event);
504  if (scaling_method > QET::FreeScaling)
505  {
506  // real, non-rounded movement from the mouse press event
507  QPointF global_movement = scene_pos - first_pos_;
508 
509  QPointF rounded_global_movement;
510  if (scaling_method == QET::SnapScalingPointToGrid)
511  {
512  // real, rounded movement from the mouse press event
513  rounded_global_movement = snapConstPointToGrid(global_movement);
514  }
515  else
516  {
517  QRectF new_bounding_rect = original_bounding_rect_;
518  applyMovementToRect(current_operation_square_, global_movement, new_bounding_rect);
519 
520  const qreal scale_epsilon = 20.0; // rounds to 0.05
521  QPointF delta = deltaForRoundScaling(original_bounding_rect_, new_bounding_rect, scale_epsilon);
522 
523  // real, rounded movement from the mouse press event
524  rounded_global_movement = global_movement + delta;
525  }
526 
527  // rounded position of the current mouse move event
528  QPointF rounded_scene_pos = first_pos_ + rounded_global_movement;
529 
530  // when scaling the selection, consider the center of the currently dragged resizing rectangle
531  QPointF current_position = mapToScene(getResizingsPoints().at(current_operation_square_));
532  // determine the final, effective movement
533  movement = rounded_scene_pos - current_position;
534  }
535 
536  QRectF bounding_rect = modified_bounding_rect_;
538  if (modified_bounding_rect_ != bounding_rect) {
540  }
541  latest_pos_ = event -> scenePos();
543 }
544 
551 {
552  Q_UNUSED(qghi);
553  Q_UNUSED(event);
554 
555  ElementEditionCommand *command = nullptr;
557  {
558  ScalePartsCommand *scale_command = new ScalePartsCommand();
559  scale_command -> setScaledPrimitives(items());
560  scale_command -> setTransformation(
563  );
564  command = scale_command;
565  }
566 
567  if (command) {
568  emit(actionFinished(command));
569  }
570 
571  adjust();
572 
574 }
575 
581 {
582  if (m_handler_vector.isEmpty() && scene())
583  {
585 
587  {
588  scene()->addItem(handler);
589  handler->setColor(Qt::darkGreen);
590  handler->installSceneEventFilter(this);
591  handler->setZValue(this->zValue()+1);
592  }
593  }
594 }
595 
601 {
602  if (!m_handler_vector.isEmpty())
603  {
604  qDeleteAll(m_handler_vector);
605  m_handler_vector.clear();
606  }
607 }
608 
619 QPointF ElementPrimitiveDecorator::deltaForRoundScaling(const QRectF &original, const QRectF &current, qreal epsilon) {
620  qreal sx = current.width() / original.width();
621  qreal sy = current.height() / original.height();
622 
623  qreal sx_rounded = QET::round(sx, epsilon);
624  qreal sy_rounded = QET::round(sy, epsilon);
625 
626  QPointF delta(
627  original.width() * (sx_rounded - sx),
628  original.height() * (sy_rounded - sy)
629  );
630  return(delta);
631 }
632 
637 QPointF ElementPrimitiveDecorator::snapConstPointToGrid(const QPointF &point) const {
638  return(
639  QPointF(
640  qRound(point.x() / grid_step_x_) * grid_step_x_,
641  qRound(point.y() / grid_step_y_) * grid_step_y_
642  )
643  );
644 }
645 
650 void ElementPrimitiveDecorator::snapPointToGrid(QPointF &point) const {
651  point.rx() = qRound(point.x() / grid_step_x_) * grid_step_x_;
652  point.ry() = qRound(point.y() / grid_step_y_) * grid_step_y_;
653 }
654 
659 bool ElementPrimitiveDecorator::mustSnapToGrid(QGraphicsSceneMouseEvent *event) {
660  return(!(event -> modifiers() & Qt::ControlModifier));
661 }
662 
670  if (event && !mustSnapToGrid(event)) {
671  return(QET::FreeScaling);
672  }
673  if (CustomElementPart *single_item = singleItem()) {
674  return single_item -> preferredScalingMethod();
675  }
676  return QET::RoundScaleRatios;
677 }
678 
685 QVariant ElementPrimitiveDecorator::itemChange(QGraphicsItem::GraphicsItemChange change, const QVariant &value)
686 {
687  if (change == ItemSceneHasChanged)
688  {
689  if(scene()) //Item is added to scene, we also add handlers
690  addHandler();
691  else //Item is removed from scene, we also remove the handlers
692  removeHandler();
693  }
694  else if (change == ItemVisibleHasChanged)
695  {
696  bool visible = value.toBool();
698  qghi->setVisible(visible);
699  }
700  else if (change == ItemZValueHasChanged && !m_handler_vector.isEmpty())
701  {
703  qghi->setZValue(this->zValue()+1);
704  }
705 
706  return QGraphicsObject::itemChange(change, value);
707 }
708 
715 bool ElementPrimitiveDecorator::sceneEventFilter(QGraphicsItem *watched, QEvent *event)
716 {
717  //Watched must be an handler
718  if(watched->type() == QetGraphicsHandlerItem::Type)
719  {
720  QetGraphicsHandlerItem *qghi = qgraphicsitem_cast<QetGraphicsHandlerItem *>(watched);
721 
722  if(m_handler_vector.contains(qghi)) //Handler must be in m_vector_index, then we can start resize
723  {
724  m_vector_index = m_handler_vector.indexOf(qghi);
725  if (m_vector_index != -1)
726  {
727  if(event->type() == QEvent::GraphicsSceneMousePress) //Click
728  {
729  handlerMousePressEvent(qghi, static_cast<QGraphicsSceneMouseEvent *>(event));
730  return true;
731  }
732  else if(event->type() == QEvent::GraphicsSceneMouseMove) //Move
733  {
734  handlerMouseMoveEvent(qghi, static_cast<QGraphicsSceneMouseEvent *>(event));
735  return true;
736  }
737  else if (event->type() == QEvent::GraphicsSceneMouseRelease) //Release
738  {
739  handlerMouseReleaseEvent(qghi, static_cast<QGraphicsSceneMouseEvent *>(event));
740  return true;
741  }
742  }
743  }
744  }
745 
746  return false;
747 }
QPointF deltaForRoundScaling(const QRectF &, const QRectF &, qreal)
void keyPressEvent(QKeyEvent *) override
QRectF modified_bounding_rect_
new bounding rect, after the user moved or resized items
snap the point used to define the new bounding rectangle to the grid
Definition: qet.h:81
void addHandler()
ElementPrimitiveDecorator::addHandler Add handlers for this item.
The QetGraphicsHandlerItem class This graphics item represents a point, destined to be used as an han...
QVector< QPointF > getResizingsPoints() const
QPointF snapConstPointToGrid(const QPointF &) const
bool moving_by_keys_
Whether we are currently moving our decorated items using the arrow keys.
void handlerMouseReleaseEvent(QetGraphicsHandlerItem *qghi, QGraphicsSceneMouseEvent *event)
ElementPrimitiveDecorator::handlerMouseReleaseEvent.
QList< CustomElementPart * > items() const
void mouseMoveEvent(QGraphicsSceneMouseEvent *) override
QPointF keys_movement_
Movement applied to our decorated items using the arrow keys.
void actionFinished(ElementEditionCommand *)
QList< QGraphicsItem * > graphicsItems() const
QET::ScalingMethod scalingMethod(QGraphicsSceneMouseEvent *)
void paint(QPainter *, const QStyleOptionGraphicsItem *, QWidget *=nullptr) override
QPointF mouse_offset_
Offset between the mouse position and the point to be snapped to grid when moving selection...
QList< CustomElementPart * > decorated_items_
void handlerMousePressEvent(QetGraphicsHandlerItem *qghi, QGraphicsSceneMouseEvent *event)
ElementPrimitiveDecorator::handlerMousePressEvent.
QVariant itemChange(GraphicsItemChange change, const QVariant &value) override
ElementPrimitiveDecorator::itemChange.
void handlerMouseMoveEvent(QetGraphicsHandlerItem *qghi, QGraphicsSceneMouseEvent *event)
ElementPrimitiveDecorator::handlerMouseMoveEvent.
The ElementEditionCommand class ElementEditionCommand is the base class for all commands classes invo...
QPointF latest_pos_
Latest point involved within the current resizing operation.
bool sceneEventFilter(QGraphicsItem *watched, QEvent *event) override
ElementPrimitiveDecorator::sceneEventFilter.
qreal round(qreal, qreal)
Definition: qet.cpp:497
adjust the scaling movement so that the induced scaling ratios are rounded
Definition: qet.h:82
QRectF effective_bounding_rect_
actual, effective bounding rect – never shrinks
void mouseReleaseEvent(QGraphicsSceneMouseEvent *) override
void applyMovementToRect(int, const QPointF &, QRectF &)
int grid_step_x_
Grid horizontal step.
void keyReleaseEvent(QKeyEvent *) override
QRectF original_bounding_rect_
original bounding rect
ScalingMethod
Supported types of interactive scaling, typically for a single element primitive. ...
Definition: qet.h:79
do not interfer with the default scaling process
Definition: qet.h:80
void adjusteHandlerPos()
ElementPrimitiveDecorator::adjusteHandlerPos.
QRectF boundingRect() const override
QPointF first_pos_
First point involved within the current resizing operation.
void removeHandler()
ElementPrimitiveDecorator::removeHandler Remove the handlers of this item.
ElementPrimitiveDecorator(QGraphicsItem *=nullptr)
void mousePressEvent(QGraphicsSceneMouseEvent *) override
int grid_step_y_
Grid horizontal step.
void setItems(const QList< QGraphicsItem *> &)
bool mustSnapToGrid(QGraphicsSceneMouseEvent *)
static QVector< QetGraphicsHandlerItem * > handlerForPoint(const QVector< QPointF > &points, int size=10)
QetGraphicsHandlerItem::handlerForPoint.
QRectF getSceneBoundingRect(QGraphicsItem *) const
CustomElementPart * singleItem() const
void scaleItems(const QRectF &, const QRectF &)
QVector< QetGraphicsHandlerItem * > m_handler_vector