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;