//Copyright 2017 Ryan Wick
//This file is part of Bandage
//Bandage is free software: you can redistribute it and/or modify
//it under the terms of the GNU General Public License as published by
//the Free Software Foundation, either version 3 of the License, or
//(at your option) any later version.
//Bandage is distributed in the hope that it will be useful,
//but WITHOUT ANY WARRANTY; without even the implied warranty of
//MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
//GNU General Public License for more details.
//You should have received a copy of the GNU General Public License
//along with Bandage. If not, see .
#include "graphicsitemedge.h"
#include "debruijnedge.h"
#include
#include
#include
#include "../program/globals.h"
#include "../program/settings.h"
#include "debruijnnode.h"
#include "ogdfnode.h"
#include
#include "graphicsitemnode.h"
GraphicsItemEdge::GraphicsItemEdge(DeBruijnEdge * deBruijnEdge, QGraphicsItem * parent) :
QGraphicsPathItem(parent), m_deBruijnEdge(deBruijnEdge)
{
calculateAndSetPath();
}
QPointF GraphicsItemEdge::extendLine(QPointF start, QPointF end, double extensionLength)
{
double extensionRatio = extensionLength / QLineF(start, end).length();
QPointF difference = end - start;
difference *= extensionRatio;
return end + difference;
}
void GraphicsItemEdge::paint(QPainter * painter, const QStyleOptionGraphicsItem *, QWidget *)
{
double edgeWidth = g_settings->edgeWidth;
QColor penColour;
if (isSelected())
penColour = g_settings->selectionColour;
else
penColour = g_settings->edgeColour;
QPen edgePen(QBrush(penColour), edgeWidth, Qt::SolidLine, Qt::RoundCap);
painter->setPen(edgePen);
painter->drawPath(path());
}
QPainterPath GraphicsItemEdge::shape() const
{
QPainterPathStroker stroker;
stroker.setWidth(g_settings->edgeWidth);
stroker.setCapStyle(Qt::RoundCap);
stroker.setJoinStyle(Qt::RoundJoin);
return stroker.createStroke(path());
}
void GraphicsItemEdge::calculateAndSetPath()
{
setControlPointLocations();
double edgeDistance = QLineF(m_startingLocation, m_endingLocation).length();
double extensionLength = g_settings->edgeLength;
if (extensionLength > edgeDistance / 2.0)
extensionLength = edgeDistance / 2.0;
m_controlPoint1 = extendLine(m_beforeStartingLocation, m_startingLocation, extensionLength);
m_controlPoint2 = extendLine(m_afterEndingLocation, m_endingLocation, extensionLength);
//If this edge is connecting a node to itself, and that node
//is made of only one line segment, then a special path is
//required, otherwise the edge will be mostly hidden underneath
//the node.
DeBruijnNode * startingNode = m_deBruijnEdge->getStartingNode();
DeBruijnNode * endingNode = m_deBruijnEdge->getEndingNode();
if (startingNode == endingNode)
{
GraphicsItemNode * graphicsItemNode = startingNode->getGraphicsItemNode();
if (graphicsItemNode == 0)
graphicsItemNode = startingNode->getReverseComplement()->getGraphicsItemNode();
if (graphicsItemNode != 0 && graphicsItemNode->m_linePoints.size() == 2)
{
makeSpecialPathConnectingNodeToSelf();
return;
}
}
//If we are in single mode and the edge connects a node to its reverse
//complement, then we need a special path to make it visible.
if (startingNode == endingNode->getReverseComplement() &&
!g_settings->doubleMode)
{
makeSpecialPathConnectingNodeToReverseComplement();
return;
}
//Otherwise, the path is just a single cubic Bezier curve.
QPainterPath path;
path.moveTo(m_startingLocation);
path.cubicTo(m_controlPoint1, m_controlPoint2, m_endingLocation);
setPath(path);
}
void GraphicsItemEdge::setControlPointLocations()
{
DeBruijnNode * startingNode = m_deBruijnEdge->getStartingNode();
DeBruijnNode * endingNode = m_deBruijnEdge->getEndingNode();
if (startingNode->hasGraphicsItem())
{
m_startingLocation = startingNode->getGraphicsItemNode()->getLast();
m_beforeStartingLocation = startingNode->getGraphicsItemNode()->getSecondLast();
}
else if (startingNode->getReverseComplement()->hasGraphicsItem())
{
m_startingLocation = startingNode->getReverseComplement()->getGraphicsItemNode()->getFirst();
m_beforeStartingLocation = startingNode->getReverseComplement()->getGraphicsItemNode()->getSecond();
}
if (endingNode->hasGraphicsItem())
{
m_endingLocation = endingNode->getGraphicsItemNode()->getFirst();
m_afterEndingLocation = endingNode->getGraphicsItemNode()->getSecond();
}
else if (endingNode->getReverseComplement()->hasGraphicsItem())
{
m_endingLocation = endingNode->getReverseComplement()->getGraphicsItemNode()->getLast();
m_afterEndingLocation = endingNode->getReverseComplement()->getGraphicsItemNode()->getSecondLast();
}
}
//This function handles the special case of an edge that connects a node
//to itself where the node graphics item has only one line segment.
void GraphicsItemEdge::makeSpecialPathConnectingNodeToSelf()
{
double extensionLength = g_settings->edgeLength;
m_controlPoint1 = extendLine(m_beforeStartingLocation, m_startingLocation, extensionLength);
m_controlPoint2 = extendLine(m_afterEndingLocation, m_endingLocation, extensionLength);
QLineF nodeLine(m_startingLocation, m_endingLocation);
QLineF normalUnitLine = nodeLine.normalVector().unitVector();
QPointF perpendicularShift = (normalUnitLine.p2() - normalUnitLine.p1()) * g_settings->edgeLength;
QPointF nodeMidPoint = (m_startingLocation + m_endingLocation) / 2.0;
QPainterPath path;
path.moveTo(m_startingLocation);
path.cubicTo(m_controlPoint1, m_controlPoint1 + perpendicularShift, nodeMidPoint + perpendicularShift);
path.cubicTo(m_controlPoint2 + perpendicularShift, m_controlPoint2, m_endingLocation);
setPath(path);
}
//This function handles the special case of an edge that connects a node to its
//reverse complement and is displayed in single mode.
void GraphicsItemEdge::makeSpecialPathConnectingNodeToReverseComplement()
{
double extensionLength = g_settings->edgeLength / 2.0;
m_controlPoint1 = extendLine(m_beforeStartingLocation, m_startingLocation, extensionLength);
m_controlPoint2 = extendLine(m_afterEndingLocation, m_endingLocation, extensionLength);
QPointF startToControl = m_controlPoint1 - m_startingLocation;
QPointF pathMidPoint = m_startingLocation + startToControl * 3.0;
QLineF normalLine = QLineF(m_controlPoint1, m_startingLocation).normalVector();
QPointF perpendicularShift = (normalLine.p2() - normalLine.p1()) * 1.5;
QPainterPath path;
path.moveTo(m_startingLocation);
path.cubicTo(m_controlPoint1, pathMidPoint + perpendicularShift, pathMidPoint);
path.cubicTo(pathMidPoint - perpendicularShift, m_controlPoint2, m_endingLocation);
setPath(path);
}