#include "xlinkobj.h"
#include <QDebug>
#include <QGraphicsScene>
#include "branchitem.h"
#include "geometry.h"
#include "math.h" // atan
#include "misc.h" // max
#include "xlink.h"
/////////////////////////////////////////////////////////////////
// XLinkObj
/////////////////////////////////////////////////////////////////
int XLinkObj::arrowSize = 6; // make instances
int XLinkObj::clickBorder = 8;
int XLinkObj::pointRadius = 10;
int XLinkObj::d_control = 300;
XLinkObj::XLinkObj(XLink *xl)
{
//qDebug()<< "Const XLinkObj (XLink)";
xlink = xl;
init();
}
XLinkObj::XLinkObj(QGraphicsItem *parent, XLink *xl) : MapObj(parent)
{
//qDebug()<< "Const XLinkObj (XLink, parent)";
xlink = xl;
init();
}
XLinkObj::~XLinkObj()
{
//qDebug() << "Destr XLinkObj";
delete (poly);
delete (path);
// If ctrl point is selected, then deleting ctrl point will
// also delete selection_ellipse
delete (c0_ellipse);
delete (c1_ellipse);
delete (endArrow);
delete (beginArrow);
}
void XLinkObj::init()
{
visBranch = nullptr;
stateVis = Hidden;
QPen pen = xlink->getPen();
QGraphicsScene *scene = xlink->getBeginBranch()->getBranchContainer()->scene();
scene->addItem(this);
path = scene->addPath(QPainterPath(), pen, Qt::NoBrush);
beginArrow = new ArrowObj(this);
beginArrow->setPen(pen);
beginArrow->setUseFixedLength(true);
beginArrow->setFixedLength(0);
endArrow = new ArrowObj(this);
endArrow->setPen(pen);
endArrow->setUseFixedLength(true);
endArrow->setFixedLength(0);
pen.setStyle(Qt::SolidLine);
poly = scene->addPolygon(QPolygonF(), pen, pen.color());
// Control points for bezier path
// (We have at least a begin branch, consider its orientation)
initC0();
initC1();
c0_ellipse = scene->addEllipse(0, 0, clickBorder * 2,
clickBorder * 2, pen, pen.color());
c1_ellipse = scene->addEllipse(0, 0, clickBorder * 2,
clickBorder * 2, pen, pen.color());
selection_ellipse = nullptr;
beginOrient = endOrient = BranchContainer::UndefinedOrientation;
pen.setWidth(1);
pen.setStyle(Qt::DashLine);
selectionTypeInt = Empty;
setVisibility(true);
}
QPointF XLinkObj::getAbsPos()
{
switch (selectionTypeInt) {
case C0:
return c0;
break;
case C1:
return c1;
break;
default:
return QPointF();
break;
}
}
void XLinkObj::setStyleBegin(const QString &s) { beginArrow->setStyleEnd(s); }
void XLinkObj::setStyleBegin(ArrowObj::OrnamentStyle os)
{
beginArrow->setStyleEnd(os);
}
ArrowObj::OrnamentStyle XLinkObj::getStyleBegin()
{
return beginArrow->getStyleEnd();
}
void XLinkObj::setStyleEnd(const QString &s) { endArrow->setStyleEnd(s); }
void XLinkObj::setStyleEnd(ArrowObj::OrnamentStyle os)
{
endArrow->setStyleEnd(os);
}
ArrowObj::OrnamentStyle XLinkObj::getStyleEnd()
{
return endArrow->getStyleEnd();
}
QPointF XLinkObj::getBeginPos() { return beginPos; }
QPointF XLinkObj::getEndPos() { return endPos; }
void XLinkObj::setEnd(QPointF p) { endPos = p; }
void XLinkObj::setSelectionType(SelectionType s)
{
selectionTypeInt = s;
updateVisibility();
}
void XLinkObj::updateGeometry()
{
QPointF a, b;
QPolygonF pa;
//qDebug() << "XLO::updateGeometry";
BranchContainer *beginBC = nullptr;
BranchContainer *endBC = nullptr;
BranchItem *bi = xlink->getBeginBranch();
if (bi)
beginBC = bi->getBranchContainer();
bi = xlink->getEndBranch();
if (bi)
endBC = bi->getBranchContainer();
/* FIXME-3 check orientation to position xlink ctrl point
if (beginBC && endBC)
qDebug() << "XLO::updateGeo beginOrient=" << beginBC->getOrientation() << " endOrient=" << endBC->getOrientation();
*/
if (beginBC) {
if (beginOrient != BranchContainer::UndefinedOrientation &&
beginOrient != beginBC->getOrientation())
c0.setX(-c0.x());
beginOrient = beginBC->getOrientation();
}
if (endBC) {
if (endOrient != BranchContainer::UndefinedOrientation &&
endOrient != endBC->getOrientation())
c1.setX(-c1.x());
endOrient = endBC->getOrientation();
}
if (visBranch) {
// Only one of the linked branches is visible
// Draw arrowhead //FIXME-5 missing shaft of arrow
BranchContainer *bc = visBranch->getBranchContainer();
if (!bc)
return;
a = b = bc->downLinkPos();
if (bc->getOrientation() == BranchContainer::RightOfParent) {
b.setX(b.x() + 2 * arrowSize);
pa.clear();
pa << a << b << QPointF(b.x(), b.y() - arrowSize)
<< QPointF(b.x() + arrowSize, b.y())
<< QPointF(b.x(), b.y() + arrowSize) << b << a;
poly->setPolygon(pa);
}
else {
b.setX(b.x() - 2 * arrowSize);
pa.clear();
pa << a << b << QPointF(b.x(), b.y() - arrowSize)
<< QPointF(b.x() - arrowSize, b.y())
<< QPointF(b.x(), b.y() + arrowSize) << b << a;
poly->setPolygon(pa);
}
}
else {
// Both linked branches are visible
// If a link is just drawn in the editor,
// we have already a beginBranch
if (beginBC)
beginPos = beginBC->downLinkPos();
if (endBC)
endPos = endBC->downLinkPos();
if (beginBC && endBC) {
beginArrow->setPos(beginPos);
beginArrow->setEndPoint(beginPos + c0);
endArrow->setPos(endPos);
endArrow->setEndPoint(endPos + c1);
}
}
// Update control points for bezier
QPainterPath p(beginPos);
p.cubicTo(beginPos + c0, endPos + c1, endPos);
clickPath = p;
path->setPath(p);
// Go back to create closed curve,
// needed for intersection check:
clickPath.cubicTo(endPos + c1, beginPos + c0, beginPos);
QPen pen = xlink->getPen();
path->setPen(pen);
poly->setBrush(pen.color());
beginArrow->setPen(pen);
endArrow->setPen(pen);
pen.setStyle(Qt::SolidLine);
c0_ellipse->setPos(beginPos + c0);
c0_ellipse->setRect(- pointRadius / 2, - pointRadius / 2, pointRadius, pointRadius);
c0_ellipse->setPen(pen);
c0_ellipse->setBrush(pen.color());
c1_ellipse->setPos(endPos + c1);
c1_ellipse->setRect(- pointRadius / 2, - pointRadius / 2, pointRadius, pointRadius);
c1_ellipse->setPen(pen);
c1_ellipse->setBrush(pen.color());
BranchItem *bi_begin = xlink->getBeginBranch();
BranchItem *bi_end = xlink->getEndBranch();
// called often during drawing, but needed during reposition
// caused by toggling scroll state
updateVisibility();
}
void XLinkObj::updateVisibility()
{
BranchContainer *beginBC = nullptr;
BranchItem *beginBI = xlink->getBeginBranch();
if (beginBI)
beginBC = beginBI->getBranchContainer();
BranchItem *endBI = xlink->getEndBranch();
BranchContainer *endBC = nullptr;
if (endBI)
endBC = endBI->getBranchContainer();
//qDebug() << "XLO::updateVis() beginBI="<<beginBI << "endBI=" << endBI;
if (beginBC && endBC) {
if (beginBC->isVisible() &&
endBC->isVisible()) { // Both ends are visible
visBranch = nullptr;
if (selectionTypeInt != Empty)
stateVis = FullShowControls;
else
stateVis = Full;
setVisibility(true);
}
else {
if (!beginBC->isVisible() &&
!endBC->isVisible()) { // None of the ends is visible
visBranch = nullptr;
stateVis = Hidden;
setVisibility(false);
}
else { // Just one end is visible, draw a symbol that shows
// that there is a link to a scrolled branch
if (beginBC->isVisible()) {
stateVis = OnlyBegin;
visBranch = beginBI;
}
else {
visBranch = endBI;
stateVis = OnlyEnd;
}
setVisibility(true);
}
}
}
}
void XLinkObj::setVisibility(bool b)
{
//qDebug() << "XLO::setVis(b) b=" << b << "stateVis=" << stateVis;
if (stateVis == FullShowControls) {
c0_ellipse->show();
c1_ellipse->show();
beginArrow->setUseFixedLength(false);
endArrow->setUseFixedLength(false);
}
else {
c0_ellipse->hide();
c1_ellipse->hide();
beginArrow->setUseFixedLength(true);
beginArrow->setFixedLength(0);
endArrow->setUseFixedLength(true);
endArrow->setFixedLength(0);
}
if (b) {
if (stateVis == OnlyBegin) {
path->hide();
poly->show();
beginArrow->hide();
endArrow->hide();
}
else if (stateVis == OnlyEnd) {
path->hide();
poly->show();
beginArrow->hide();
endArrow->hide();
}
else {
path->show();
poly->hide();
beginArrow->show();
endArrow->show();
}
}
else {
poly->hide();
path->hide();
beginArrow->hide();
endArrow->hide();
}
}
void XLinkObj::initC0()
{
if (!xlink)
return;
BranchItem *beginBranch = xlink->getBeginBranch();
if (!beginBranch)
return;
BranchContainer *bc = beginBranch->getBranchContainer();
if (!bc)
return;
if (bc->getOrientation() == BranchContainer::RightOfParent)
c0 = QPointF(d_control, 0);
else
c0 = QPointF(-d_control, 0);
}
void XLinkObj::initC1()
{
if (!xlink)
return;
BranchItem *endBranch = xlink->getEndBranch();
if (!endBranch)
return;
BranchContainer *bc =endBranch->getBranchContainer();
if (!bc)
return;
if (bc->getOrientation() == BranchContainer::RightOfParent)
c1 = QPointF(d_control, 0);
else
c1 = QPointF(-d_control, 0);
}
void XLinkObj::setC0(const QPointF &p) { c0 = p; }
QPointF XLinkObj::getC0() { return c0; }
void XLinkObj::setC1(const QPointF &p)
{
c1 = p;
}
QPointF XLinkObj::getC1() { return c1; }
void XLinkObj::setSelectedCtrlPoint(const QPointF &p)
{
switch (selectionTypeInt) {
case C0:
c0 = p - beginPos;
break;
case C1:
c1 = p - endPos;
break;
default:
break; }
updateGeometry();
}
QRectF XLinkObj::boundingRect() {
// Return bounding box in scene coordinates of selected ctrl point
switch (selectionTypeInt) {
case C0:
[[fallthrough]]; // intentional fallthrough
case C1:
return selection_ellipse->mapToScene(selection_ellipse->boundingRect()).boundingRect();
default:
return QRectF();
}
}
XLinkObj::SelectionType XLinkObj::couldSelect(const QPointF &p)
{
QPointF v;
qreal d;
qreal d_max = 10;
switch (stateVis) {
case FullShowControls:
v = c0_ellipse->pos() - p;
d = Geometry::distance(c0_ellipse->pos(), p);
if (d < d_max) {
setSelectionType(C0);
return C0;
}
v = c1_ellipse->pos() - p;
d = Geometry::distance(c1_ellipse->pos(), p);
if (d < d_max) {
setSelectionType(C1);
return C1;
}
break;
case OnlyBegin:
case OnlyEnd:
// not selected, only partially visible
/*
if (poly->boundingRect().contains(p))
b = true;
*/
break;
default:
// not selected, but path is fully visible
QRectF r(p.x() - clickBorder,
p.y() - clickBorder,
clickBorder * 2,
clickBorder * 2);
if (clickPath.intersects(r))
return Path;
}
return XLinkObj::Empty;
}
void XLinkObj::select(const QPen &pen, const QBrush &brush)
{
if (!selection_ellipse) {
QGraphicsScene *scene = xlink->getBeginBranch()->getBranchContainer()->scene();
qreal r = clickBorder * 2.5;
selection_ellipse = scene->addEllipse(-r / 2 , -r / 2, r, r, pen, brush);
selection_ellipse->setFlag(QGraphicsItem::ItemStacksBehindParent);
}
switch (selectionTypeInt) {
case C0:
selection_ellipse->setParentItem(c0_ellipse);
break;
case C1:
selection_ellipse->setParentItem(c1_ellipse);
break;
default:
break;
}
}
void XLinkObj::unselect()
{
delete selection_ellipse;
selection_ellipse = nullptr;
setSelectionType(Empty);
}