Jepessen
Jepessen

Reputation: 12415

Refresh error when dragging a line in QSceneView

I need to draw a connector between two items in a QSceneView. I've subclssed QGraphicsObject for creating this line (it's not a QGraphicsLineItem because I need to update its appearance later). This is the class:

#ifndef TRANSITIONDRAGLINE_HPP_
#define TRANSITIONDRAGLINE_HPP_

#include "Style/CanvasTransitionDragLine.hpp"
#include <QGraphicsObject>
#include <QPointF>
#include <string>

class TransitionDragLine : public QGraphicsObject {

  Q_OBJECT

public:

  TransitionDragLine(const Style::CanvasTransitionDragLine& style, QGraphicsItem* parent = nullptr);
  virtual ~TransitionDragLine();
  void setStartPoint(const QPointF& startPoint);
  void setEndPoint(const QPointF& endPoint);
public:

  QRectF boundingRect() const override;
  void paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget) override;

private:

  Style::CanvasTransitionDragLine m_style;
  QPointF m_startPoint;
  QPointF m_endPoint;
};

#endif // !TRANSITIONDRAGLINE_HPP_
#include "TransitionDragLine.hpp"
#include <QPainter>

///////////////////////////////////////////////////////////////////////////////
// PUBLIC SECTION                                                            //
///////////////////////////////////////////////////////////////////////////////

TransitionDragLine::TransitionDragLine(const Style::CanvasTransitionDragLine& style, QGraphicsItem* parent) :
  QGraphicsObject(parent),
  m_style(style) {

}

TransitionDragLine::~TransitionDragLine() {

}

void TransitionDragLine::setStartPoint(const QPointF& startPoint) {
  m_startPoint = startPoint;
}

void TransitionDragLine::setEndPoint(const QPointF& endPoint) {
  m_endPoint = endPoint;
  update();
}

///////////////////////////////////////////////////////////////////////////////
// VIRTUAL PUBLIC SECTION                                                    //
///////////////////////////////////////////////////////////////////////////////

QRectF TransitionDragLine::boundingRect() const {
  qreal dx = m_endPoint.x() - m_startPoint.x();
  qreal dy = m_endPoint.y() - m_startPoint.y();
  qreal x{ 0.0 };
  qreal y{ 0.0 };
  qreal w{ 0.0 };
  qreal h{ 0.0 };
  qreal penHalfWidth{ m_style.getPen().widthF() * 0.5 };

  if (dx >= 0.0 && dy >= 0.0) {
    x = 0.0 - penHalfWidth;
    y = 0.0 - penHalfWidth;
    w = dx + penHalfWidth;
    h = dy + penHalfWidth;
  }
  else if (dx >= 0.0 && dy < 0.0) {
    x = 0.0 - penHalfWidth;
    y = dy - penHalfWidth;
    w = dx - penHalfWidth;
    h = -dy - penHalfWidth;
  }
  else if (dx < 0.0 && dy >= 0.0) {
    x = dx - penHalfWidth;
    y = 0 - penHalfWidth;
    w = -dx - penHalfWidth;
    h = dy - penHalfWidth;
  }
  else {
    x = dx - penHalfWidth;
    y = dy - penHalfWidth;
    w = -dx - penHalfWidth;
    h = -dy - penHalfWidth;
  }
  return QRectF(x, y, w, h);
}
void TransitionDragLine::paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget) {
  qreal px = m_endPoint.x() - m_startPoint.x();
  qreal py = m_endPoint.y() - m_startPoint.y();
  painter->setPen(m_style.getPen());
  painter->drawLine(0.0, 0.0, px, py);
}

When I want to use it, I set a flag in my Canvas class, that inherits QGraphicsView, and I update it. When I press the mouse button I set the start point and then I set the endpoint (and call update()), every time that I move the mouse until left button is pressed. Basically I call startTransitionLineDrag in polling until the mouse is pressed, and when I release the mouse button I call endTransitionLineDrag in my Canvas (subclass of QGraphicsView):

void Canvas::mouseMoveEvent(QMouseEvent* event) {
  switch (m_currentState) {
  case CurrentState::PressedOnTransition: {
    if (event->buttons() == Qt::LeftButton) {
      startTransitionLineDrag(event);
    }
  } break;
  }
  QGraphicsView::mouseMoveEvent(event);
}

void Canvas::mouseReleaseEvent(QMouseEvent* event) {
  if (m_currentState == CurrentState::PressedOnTransition) {
    if (true /* business logic here not useful for the problem */) {
      endTransitionLineDrag(event);
    }
  }
  QGraphicsView::mouseReleaseEvent(event);
}

void Canvas::startTransitionLineDrag(QMouseEvent* event) {
  if (m_transitionDragLine == nullptr) {
    m_transitionDragLine = new TransitionDragLine(m_style.getTransitionDragLineStyle());
    m_transitionDragLine->setStartPoint(mapToScene(event->pos()));
    m_scene->addItem(m_transitionDragLine);
    m_transitionDragLine->setPos(mapToScene(event->pos()));
  }
  m_transitionDragLine->setEndPoint(mapToScene(event->pos()));
  //repaint();
}

void Canvas::endTransitionLineDrag(QMouseEvent* event) {
  /* other business logic */
  deleteDragTransitionLine();
}

void Canvas::deleteDragTransitionLine() {
  if (m_transitionDragLine) {
    m_scene->removeItem(m_transitionDragLine);
    delete m_transitionDragLine;
    m_transitionDragLine = nullptr;
  }
}

The logic works: when I activate the dragging I can see the line and it's update until the mouse button is pressed. But you can see in the attached image that I've a rendering problem:

dragline defect

The rendering is not working properly for the line; I've a trail of past images of the line, like the QGraphicsView is not updated properly.

I sense a code smell when in TransitionDragLine::setEndPoint I need to call update() after setting the end point of the line (otherwise the line is not displayed) but I don't find a way to solve the issue.

What I'm doing wrong?

EDIT:

I've seen a solution here:

Artifacts showing when modifying a custom QGraphicsItem

I've tried to call prepareGeometryChange() every time that I update the end point but artifacts remain:

void TransitionDragLine::setEndPoint(const QPointF& endPoint) {
  m_endPoint = endPoint;
  prepareGeometryChange();
  update();
}

Upvotes: 0

Views: 166

Answers (1)

pasbi
pasbi

Reputation: 2179

It looks like your implementation of QRectF TransitionDragLine::boundingRect() const is incorrect. It can be a bit tricky to get it right since multiple coordinate systems are involved.

The proper solution would be to get the bounding boxes right, but for small scenes, it's usually good enough to set the QGraphicsView::updateMode to QGraphicsView::FullViewportUpdate. That is, QGraphicsView will redraw the whole viewport rather than just the bounding box area.

Upvotes: 1

Related Questions