File indexing completed on 2024-05-19 04:36:35

0001 /* This file is part of the TikZKit project.
0002  *
0003  * Copyright (C) 2013-2014 Dominik Haumann <dhaumann@kde.org>
0004  *
0005  * This library is free software; you can redistribute it and/or modify
0006  * it under the terms of the GNU Library General Public License as published
0007  * by the Free Software Foundation, either version 2 of the License, or
0008  * (at your option) any later version.
0009  *
0010  * This library is distributed in the hope that it will be useful,
0011  * but WITHOUT ANY WARRANTY; without even the implied warranty of
0012  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
0013  * GNU Library General Public License for more details.
0014  *
0015  * You should have received a copy of the GNU Library General Public License
0016  * along with this library; see the file COPYING.LIB.  If not, see
0017  * <http://www.gnu.org/licenses/>.
0018  */
0019 
0020 #include "EdgePathItem.h"
0021 
0022 #include "NodeItem.h"
0023 #include "DocumentPrivate.h"
0024 #include "AbstractArrow.h"
0025 #include "Painter.h"
0026 
0027 #include <tikz/core/EdgePath.h>
0028 #include <tikz/core/Style.h>
0029 
0030 #include <QPainter>
0031 #include <QDebug>
0032 #include <QPainterPathStroker>
0033 
0034 #include <cmath>
0035 
0036 namespace tikz {
0037 namespace ui {
0038 
0039 EdgePathItem::EdgePathItem(tikz::core::Path * path, QGraphicsItem * parent)
0040     : PathItem(path, parent)
0041     , m_dirty(true)
0042     , m_arrowTail(new AbstractArrow(style()))
0043     , m_arrowHead(new AbstractArrow(style()))
0044 {
0045     setFlag(QGraphicsItem::ItemIsSelectable, true);
0046     setCacheMode(QGraphicsItem::DeviceCoordinateCache);
0047 
0048     // forward changed signal
0049     connect(path, SIGNAL(changed()), this, SLOT(slotUpdate()));
0050 
0051     // make sure the start and end node are tracked
0052     connect(path, SIGNAL(startNodeChanged(tikz::core::Node*)),
0053             this, SLOT(updateStartNode(tikz::core::Node*)));
0054     connect(path, SIGNAL(endNodeChanged(tikz::core::Node*)),
0055             this, SLOT(updateEndNode(tikz::core::Node*)));
0056 }
0057 
0058 EdgePathItem::~EdgePathItem()
0059 {
0060     delete m_arrowHead;
0061     delete m_arrowTail;
0062 }
0063 
0064 tikz::core::EdgePath * EdgePathItem::edgePath() const
0065 {
0066     tikz::core::EdgePath * p = qobject_cast<tikz::core::EdgePath*>(path());
0067     Q_ASSERT(p != nullptr);
0068 
0069     return p;
0070 }
0071 
0072 void EdgePathItem::updateStartNode(tikz::core::Node * node)
0073 {
0074     NodeItem * newNode = nullptr;
0075 
0076     if (node) {
0077         newNode = document()->nodeItemFromId(node->uid());
0078     }
0079 
0080     if (m_startNode != newNode) {
0081         prepareGeometryChange();
0082         m_dirty = true;
0083         m_startNode = newNode;
0084     }
0085 }
0086 
0087 void EdgePathItem::updateEndNode(tikz::core::Node * node)
0088 {
0089     NodeItem * newNode = nullptr;
0090 
0091     if (node) {
0092         newNode = document()->nodeItemFromId(node->uid());
0093     }
0094 
0095     if (m_endNode != newNode) {
0096         prepareGeometryChange();
0097         m_dirty = true;
0098         m_endNode = newNode;
0099     }
0100 }
0101 
0102 void EdgePathItem::setStartNode(NodeItem * start)
0103 {
0104     if (m_startNode != start) {
0105         edgePath()->setStartNode(start ? start->node() : nullptr);
0106     }
0107 
0108     // m_startNode was fixed through updateStartNode()
0109     Q_ASSERT(m_startNode == start);
0110 }
0111 
0112 void EdgePathItem::setEndNode(NodeItem * end)
0113 {
0114     if (m_endNode != end) {
0115         edgePath()->setEndNode(end ? end->node() : nullptr);
0116     }
0117 
0118     // m_startNode was fixed through updateStartNode()
0119     Q_ASSERT(m_endNode == end);
0120 }
0121 
0122 NodeItem* EdgePathItem::startNode() const
0123 {
0124     return m_startNode;
0125 }
0126 
0127 NodeItem* EdgePathItem::endNode() const
0128 {
0129     return m_endNode;
0130 }
0131 
0132 QPointF EdgePathItem::startPos() const
0133 {
0134     return startPos(startAngle());
0135 }
0136 
0137 QPointF EdgePathItem::startPos(qreal rad) const
0138 {
0139     if (m_startNode) {
0140         return mapFromScene(m_startNode->contactPoint(startAnchor(), rad));
0141     } else {
0142         return mapFromScene(edgePath()->startPos());
0143     }
0144 }
0145 
0146 QPointF EdgePathItem::endPos() const
0147 {
0148     return endPos(endAngle());
0149 }
0150 
0151 QPointF EdgePathItem::endPos(qreal rad) const
0152 {
0153     if (m_endNode) {
0154         return mapFromScene(m_endNode->contactPoint(endAnchor(), rad));
0155     } else {
0156         return mapFromScene(edgePath()->endPos());
0157     }
0158 }
0159 
0160 QString EdgePathItem::startAnchor() const
0161 {
0162     return edgePath()->startAnchor();
0163 }
0164 
0165 QString EdgePathItem::endAnchor() const
0166 {
0167     return edgePath()->endAnchor();
0168 }
0169 
0170 void EdgePathItem::setStartAnchor(const QString & anchor)
0171 {
0172     edgePath()->setStartAnchor(anchor);
0173 }
0174 
0175 void EdgePathItem::setEndAnchor(const QString & anchor)
0176 {
0177     edgePath()->setEndAnchor(anchor);
0178 }
0179 
0180 qreal EdgePathItem::baseAngle() const
0181 {
0182     const QPointF startPos =
0183         m_startNode ? mapFromScene(m_startNode->anchor(startAnchor()))
0184               : mapFromScene(edgePath()->startPos());
0185 
0186     const QPointF endPos =
0187         m_endNode ? mapFromScene(m_endNode->anchor(endAnchor()))
0188             : mapFromScene(edgePath()->endPos());
0189 
0190     const QPointF diff = endPos - startPos;
0191     return std::atan2(diff.y(), diff.x());
0192 }
0193 
0194 qreal EdgePathItem::startAngle() const
0195 {
0196     return baseAngle();
0197 }
0198 
0199 qreal EdgePathItem::endAngle() const
0200 {
0201     return baseAngle() - M_PI;
0202 }
0203 
0204 void EdgePathItem::slotUpdate()
0205 {
0206     // propagate change in geometry
0207     prepareGeometryChange();
0208 
0209     // mark as dirty
0210     m_dirty = true;
0211 
0212     // absolutely necessary to request repaint
0213     update();
0214 }
0215 
0216 void EdgePathItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
0217 {
0218     Q_UNUSED(widget);
0219     Q_UNUSED(option);
0220 
0221     updateCache();
0222 
0223     painter->save();
0224     painter->setRenderHints(QPainter::Antialiasing);
0225 
0226     Painter p(painter, style());
0227     QPen pen = p.pen();
0228 
0229     if (isHovered()) {
0230         painter->save();
0231         QPen p(Qt::darkBlue);
0232         p.setWidth(0);
0233         painter->setPen(p);
0234         painter->drawPath(m_hoverPath);
0235         painter->restore();
0236         painter->fillPath(m_hoverPath, QColor(148, 202, 239));
0237 
0238     }
0239 
0240     // draw line
0241     p.drawPath(m_edgePath);
0242 
0243     // draw arrows
0244     pen.setStyle(Qt::SolidLine);
0245     painter->setPen(pen);
0246     painter->save();
0247         painter->translate(m_startAnchor.x(), m_startAnchor.y());
0248         painter->rotate(180 - m_edgePath.angleAtPercent(0.0));
0249         m_arrowTail->draw(painter);
0250     painter->restore();
0251     painter->save();
0252         painter->translate(m_endAnchor.x(), m_endAnchor.y());
0253         painter->rotate(-m_edgePath.angleAtPercent(1.0));
0254         m_arrowHead->draw(painter);
0255     painter->restore();
0256 
0257     // debug cached paths (already transformed to logical coordinates)
0258 //     painter->drawPath(d->m_tailPath);
0259 //     painter->drawPath(d->m_headPath);
0260 
0261     // TODO: create d->paths
0262 //     if (isHovered()) {
0263 //         QPointF startAnchor = startPos();
0264 //         QPointF endAnchor = endPos();
0265 //         QPointF diff(endAnchor - startAnchor);
0266 //         const qreal radAngle = std::atan2(diff.y(), diff.x());
0267 //         d->drawHandle(painter, startAnchor, m_startNode != 0);
0268 //         d->drawHandle(painter, endAnchor, m_endNode != 0);
0269 //     }
0270 
0271     // debug: draw bounding rect:
0272 //     painter->setBrush(Qt::NoBrush);
0273 //     painter->drawRect(boundingRect());
0274 
0275     painter->restore();
0276 }
0277 
0278 QRectF EdgePathItem::boundingRect() const
0279 {
0280     // make sure the start and end nodes positions are up-to-date
0281     const_cast<EdgePathItem*>(this)->updateCache();
0282 
0283     QRectF br = m_hoverPath.boundingRect();
0284     br = br.normalized();
0285     br.adjust(-0.05, -0.05, 0.05, 0.05);
0286     return br;
0287 }
0288 
0289 QPainterPath EdgePathItem::shape() const
0290 {
0291     const_cast<EdgePathItem*>(this)->updateCache();
0292 
0293     return m_shapePath;
0294 }
0295 
0296 bool EdgePathItem::contains(const QPointF & point) const
0297 {
0298     const_cast<EdgePathItem*>(this)->updateCache();
0299 
0300     return m_hoverPath.contains(point);
0301 }
0302 
0303 void EdgePathItem::updateCache()
0304 {
0305     if (! m_dirty) {
0306         return;
0307     }
0308     m_dirty = false;
0309 
0310     // update arrow head and arrow tail if needed
0311     if (m_arrowTail->type() != style()->arrowTail()) {
0312         delete m_arrowTail;
0313         m_arrowTail = ::createArrow(style()->arrowTail(), style());
0314     }
0315     if (m_arrowHead->type() != style()->arrowHead()) {
0316         delete m_arrowHead;
0317         m_arrowHead = ::createArrow(style()->arrowHead(), style());
0318     }
0319 
0320     // reset old paths
0321     m_edgePath = QPainterPath();
0322     m_headPath = QPainterPath();
0323     m_tailPath = QPainterPath();
0324 
0325     // compute shorten < and shorten > so it can be used to adapt m_startAnchor and m_endAnchor
0326     const qreal shortenStart = style()->shortenStart().toPoint() + m_arrowTail->rightExtend();
0327     const qreal shortenEnd = style()->shortenEnd().toPoint() + m_arrowHead->rightExtend();
0328 
0329     const qreal startRad = startAngle();
0330     const qreal endRad = endAngle();
0331 
0332     // final line
0333     m_startAnchor = startPos() + shortenStart * QPointF(std::cos(startRad), std::sin(startRad));
0334     m_endAnchor = endPos() + shortenEnd * QPointF(std::cos(endRad), std::sin(endRad));
0335 
0336     // create line: first vertical, then horizontal
0337     m_edgePath.moveTo(m_startAnchor);
0338     m_edgePath.lineTo(m_endAnchor);
0339 
0340     //
0341     // update arrow tail + arrow head
0342     //
0343     QTransform tailTrans;
0344     tailTrans.translate(m_startAnchor.x(), m_startAnchor.y());
0345     tailTrans.rotate(180 - m_edgePath.angleAtPercent(0.0));
0346     m_tailPath = tailTrans.map(m_arrowTail->path());
0347 
0348     QTransform headTrans;
0349     headTrans.translate(m_endAnchor.x(), m_endAnchor.y());
0350     headTrans.rotate(-m_edgePath.angleAtPercent(1.0));
0351     m_headPath = headTrans.map(m_arrowHead->path());
0352 
0353     //
0354     // cache hover and shape path
0355     //
0356     QPainterPathStroker pps;
0357     pps.setJoinStyle(Qt::RoundJoin);
0358     pps.setCapStyle(Qt::FlatCap);
0359 
0360     tikz::Value w = 1.0_mm;
0361     pps.setWidth(style()->penWidth().toPoint() + w.toPoint());
0362     m_hoverPath = pps.createStroke(m_edgePath);
0363     m_hoverPath.addPath(headTrans.map(m_arrowHead->contour(w.toPoint())));
0364     m_hoverPath.addPath(tailTrans.map(m_arrowTail->contour(w.toPoint())));
0365 
0366     w = 2.0_mm;
0367     pps.setWidth(style()->penWidth().toPoint() + w.toPoint());
0368     m_shapePath = pps.createStroke(m_edgePath);
0369     m_shapePath.addPath(headTrans.map(m_arrowHead->contour(w.toPoint())));
0370     m_shapePath.addPath(tailTrans.map(m_arrowTail->contour(w.toPoint())));
0371 }
0372 
0373 }
0374 }
0375 
0376 // kate: indent-width 4; replace-tabs on;