#ifdef _WIN32
//See http://www.richelbilderbeek.nl/CppCompileErrorSwprintfHasNotBeenDeclared.htm
#undef __STRICT_ANSI__
#endif
//#include own header file as first substantive line of code, from:
// * John Lakos. Large-Scale C++ Software Design. 1996. ISBN: 0-201-63362-0. Section 3.2, page 110
#include "qtpathitem.h"
#include <cassert>
#include <cmath>
#include <QCursor>
#include <QGraphicsSceneMouseEvent>
#include <QKeyEvent>
#include <QPainter>
#include "qtrectitem.h"
#include <boost/geometry.hpp>
#include <boost/geometry/geometries/linestring.hpp>
#include <boost/geometry/geometries/point_xy.hpp>
///Obtain the zero or one intersections between two finite lines
//From http://www.richelbilderbeek.nl/CppGetLineLineIntersections.htm
template <class T>
const std::vector<
boost::geometry::model::d2::point_xy<T>
>
GetLineLineIntersections(
const boost::geometry::model::linestring<
boost::geometry::model::d2::point_xy<T>
> line1,
const boost::geometry::model::linestring<
boost::geometry::model::d2::point_xy<T>
> line2)
{
typedef boost::geometry::model::d2::point_xy<T> Point;
typedef boost::geometry::model::linestring<Point> Line;
std::vector<Point> points;
boost::geometry::intersection(line1,line2,points);
assert(points.empty() || points.size() == 1);
return points;
}
//Helper function to create a const line without a temporary std::vector
template <class T>
const boost::geometry::model::linestring<boost::geometry::model::d2::point_xy<T>
>
CreateLine(const std::vector<boost::geometry::model::d2::point_xy<T> >& v)
{
return boost::geometry::model::linestring<
boost::geometry::model::d2::point_xy<T>
>(v.begin(),v.end());
}
///Obtain the zero, one or two intersections between a line and a rectanle
//From http://www.richelbilderbeek.nl/CppGetLineRectIntersections.htm
template <class T>
const std::vector<
boost::geometry::model::d2::point_xy<T>
>
GetLineRectIntersections(
const boost::geometry::model::linestring<
boost::geometry::model::d2::point_xy<T>
> line,
const boost::geometry::model::box<
boost::geometry::model::d2::point_xy<T>
> rect)
{
typedef boost::geometry::model::d2::point_xy<T> Point;
typedef boost::geometry::model::linestring<Point> Line;
typedef boost::geometry::model::box<Point> Rect;
const Point p0 = Point(rect.min_corner().x(), rect.min_corner().y());
const Point p1 = Point(rect.max_corner().x(), rect.min_corner().y());
const Point p2 = Point(rect.min_corner().x(), rect.max_corner().y());
const Point p3 = Point(rect.max_corner().x(), rect.max_corner().y());
const std::vector<Line> lines
=
{
CreateLine(std::vector<Point>( {p0,p1} )),
CreateLine(std::vector<Point>( {p0,p2} )),
CreateLine(std::vector<Point>( {p1,p3} )),
CreateLine(std::vector<Point>( {p2,p3} ))
};
std::vector<Point> points;
std::for_each(lines.begin(),lines.end(),
[&points,line](const Line& side)
{
const std::vector<Point> v = GetLineLineIntersections(line,side);
std::copy(v.begin(),v.end(),std::back_inserter(points));
}
);
//Remove doublures
//Put 'typename' before 'std::vector<Point>::iteratortype' to prevent getting the error below:
//error: need 'typename' before 'std::vector<boost::geometry::model::d2::point_xy<T> >::iterator'
// because 'std::vector<boost::geometry::model::d2::point_xy<T> >' is a dependent scope
typename std::vector<Point>::iterator new_end = std::unique( points.begin(),points.end(),
[](const Point& lhs, const Point& rhs)
{
return lhs.x() == rhs.x() && lhs.y() == rhs.y();
}
);
points.erase(new_end,points.end());
assert(points.size() <= 2);
return points;
}
const double QtPathItem::m_click_easy_width = 10.0;
QtPathItem::QtPathItem(
const QtRectItem * const from,
const bool tail,
const QtRectItem * const mid,
const bool head,
const QtRectItem * const to,
QGraphicsItem *parent, QGraphicsScene *scene)
: QGraphicsItem(parent,scene),
m_from(from),
m_head(head),
m_mid(mid),
m_tail(tail),
m_to(to)
{
this->setFlags(QGraphicsItem::ItemIsSelectable);
assert(!(flags() & QGraphicsItem::ItemIsMovable) );
assert( (flags() & QGraphicsItem::ItemIsSelectable) );
//Accept enterHoverEvents
this->setAcceptHoverEvents(true);
//Put this arrow item under the rect
this->setZValue(mid->zValue() - 1.0);
}
QRectF QtPathItem::boundingRect() const
{
return shape().boundingRect();
}
double QtPathItem::GetAngle(const double dx, const double dy)
{
return M_PI - (std::atan(dx/dy));
}
QPointF QtPathItem::GetBeyond() const
{
const QPointF center = GetCenter();
const double dx_mid_center = m_mid->pos().x() - center.x();
const double dy_mid_center = m_mid->pos().y() - center.y();
const QPointF beyond(center.x() + dx_mid_center + dx_mid_center, center.y() + dy_mid_center + dy_mid_center);
return beyond;
}
QPointF QtPathItem::GetCenter() const
{
const QPointF center((m_from->pos() + m_to->pos()) / 2.0);
return center;
}
QPointF QtPathItem::GetHead() const
{
typedef boost::geometry::model::d2::point_xy<double> Point;
typedef boost::geometry::model::linestring<Point> Line;
typedef boost::geometry::model::box<Point> Rect;
const QPointF beyond = GetBeyond();
const Line line_head = CreateLine(
std::vector<Point>(
{
Point(beyond.x(),beyond.y()),
Point(m_to->pos().x(),m_to->pos().y()),
}
)
);
const QRectF qr_to = m_to->boundingRect().translated(m_to->pos());
const Rect r_to(
Point(qr_to.topLeft().x() ,qr_to.topLeft().y() ),
Point(qr_to.bottomRight().x(),qr_to.bottomRight().y())
);
std::vector<Point> p_head_end = GetLineRectIntersections(line_head,r_to);
if (p_head_end.empty())
{
p_head_end.push_back(Point(m_to->pos().x(),m_to->pos().y()));
//Yes,it happens, when the line does not leave the rectangle
//this happens when the two node rectanges overlap
}
assert(!p_head_end.empty());
assert(p_head_end.size() == 1);
return QPointF(p_head_end[0].x(),p_head_end[0].y());
}
QPointF QtPathItem::GetTail() const
{
typedef boost::geometry::model::d2::point_xy<double> Point;
typedef boost::geometry::model::linestring<Point> Line;
typedef boost::geometry::model::box<Point> Rect;
const QPointF beyond = GetBeyond();
const Line line_tail = CreateLine(
std::vector<Point>(
{
Point(m_from->pos().x(),m_from->pos().y()),
Point(beyond.x(),beyond.y()),
}
)
);
const QRectF qr_from = m_from->boundingRect().translated(m_from->pos());
const Rect r_from(
Point(qr_from.topLeft().x() ,qr_from.topLeft().y() ),
Point(qr_from.bottomRight().x(),qr_from.bottomRight().y())
);
std::vector<Point> p_tail_end = GetLineRectIntersections(line_tail,r_from);
if (p_tail_end.empty())
{
//Yes,it happens, when the line does not leave the rectangle
//this happens when the two node rectanges overlap
p_tail_end.push_back(Point(m_from->pos().x(),m_from->pos().y()));
}
assert(!p_tail_end.empty());
assert(p_tail_end.size() == 1);
return QPointF(p_tail_end[0].x(),p_tail_end[0].y());
}
void QtPathItem::hoverEnterEvent(QGraphicsSceneHoverEvent *)
{
this->setCursor(QCursor(Qt::PointingHandCursor));
}
void QtPathItem::keyPressEvent(QKeyEvent *event)
{
switch (event->key())
{
case Qt::Key_F1:
case Qt::Key_1:
case Qt::Key_T:
case Qt::Key_Minus:
m_tail = !m_tail;
this->update();
break;
case Qt::Key_F2:
case Qt::Key_2:
case Qt::Key_H:
case Qt::Key_Plus:
m_head = !m_head;
this->update();
break;
default:
break;
}
QGraphicsItem::keyPressEvent(event);
}
void QtPathItem::mousePressEvent(QGraphicsSceneMouseEvent *event)
{
if (event->modifiers() & Qt::ShiftModifier)
{
if ((event->pos() - this->m_from->pos()).manhattanLength() < 10.0)
{
m_tail = !m_tail;
this->update();
}
else if ((event->pos() - this->m_to->pos()).manhattanLength() < 10.0)
{
m_head = !m_head;
this->update();
}
}
QGraphicsItem::mousePressEvent(event);
}
void QtPathItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *, QWidget *)
{
painter->setRenderHint(QPainter::Antialiasing);
if (this->isSelected())
{
const QColor color(255,0,0);
QPen pen;
pen.setColor(color);
pen.setWidth(3);
painter->setPen(pen);
}
else
{
const QColor color(0,0,0);
QPen pen;
pen.setColor(color);
pen.setWidth(1);
painter->setPen(pen);
}
//Line must go _though_ mid pos, instead of using it as a virtual hinge point
//Solution:
// - define point 'center' as the middle between from and to
// - define point 'beyond' as the mirror point of 'center', using mid_pos as a mirror
const QPointF beyond = GetBeyond();
const QPointF p_tail_end = GetTail();
const QPointF p_head_end = GetHead();
QPainterPath curve;
curve.moveTo(p_tail_end);
curve.quadTo(beyond,p_head_end);
painter->drawPath(curve);
{
const double sz = 10.0; //pixels
if (m_tail)
{
//The angle from midpoint to tail
//Thanks goes out to Toine van den Bogaart and Theo van den Bogaart for being happy to help with the math
const double dx = beyond.x() - m_from->pos().x();
const double dy = beyond.y() - m_from->pos().y();
double angle = GetAngle(dx,dy);
if (dy >= 0.0) angle = (1.0 * M_PI) + angle;
const QPointF p0(p_tail_end.x(),p_tail_end.y());
const QPointF p1
= p0 + QPointF(
std::sin(angle + M_PI + (M_PI * 0.1)) * sz,
-std::cos(angle + M_PI + (M_PI * 0.1)) * sz);
const QPointF p2
= p0 + QPointF(
std::sin(angle + M_PI - (M_PI * 0.1)) * sz,
-std::cos(angle + M_PI - (M_PI * 0.1)) * sz);
painter->drawPolygon(QPolygonF() << p0 << p1 << p2);
}
if (m_head)
{
//The angle from midpoint to head
//Thanks goes out to Toine van den Bogaart and Theo van den Bogaart for being happy to help with the math
const double dx = m_to->pos().x() - beyond.x();
const double dy = m_to->pos().y() - beyond.y();
double angle = GetAngle(dx,dy);
if (dy >= 0.0) angle = (1.0 * M_PI) + angle;
const QPointF p0(p_head_end.x(),p_head_end.y());
const QPointF p1
= p0 + QPointF(
std::sin(angle + 0.0 + (M_PI * 0.1)) * sz,
-std::cos(angle + 0.0 + (M_PI * 0.1)) * sz);
const QPointF p2
= p0 + QPointF(
std::sin(angle + 0.0 - (M_PI * 0.1)) * sz,
-std::cos(angle + 0.0 - (M_PI * 0.1)) * sz);
painter->drawPolygon(QPolygonF() << p0 << p1 << p2);
}
}
}
QPainterPath QtPathItem::shape() const
{
const QPointF beyond = GetBeyond();
const QPointF p_tail_end = GetTail();
const QPointF p_head_end = GetHead();
QPainterPath curve;
curve.moveTo(p_tail_end);
curve.quadTo(beyond,p_head_end);
QPainterPathStroker stroker;
stroker.setWidth(m_click_easy_width);
return stroker.createStroke(curve);
}
|