File indexing completed on 2024-05-19 15:27:45

0001 /* This file is part of KGraphViewer.
0002    Copyright (C) 2005-2007 Gael de Chalendar <kleag@free.fr>
0003 
0004    KGraphViewer is free software; you can redistribute it and/or
0005    modify it under the terms of the GNU General Public
0006    License as published by the Free Software Foundation, version 2.
0007 
0008    This program is distributed in the hope that it will be useful,
0009    but WITHOUT ANY WARRANTY; without even the implied warranty of
0010    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
0011    General Public License for more details.
0012 
0013    You should have received a copy of the GNU General Public License
0014    along with this program; if not, write to the Free Software
0015    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
0016    02110-1301, USA
0017 */
0018 
0019 /* This file was callgraphview.cpp, part of KCachegrind.
0020    Copyright (C) 2003 Josef Weidendorfer <Josef.Weidendorfer@gmx.de>
0021 
0022    KCachegrind is free software; you can redistribute it and/or
0023    modify it under the terms of the GNU General Public
0024    License as published by the Free Software Foundation, version 2.
0025 */
0026 
0027 #include "canvasedge.h"
0028 #include "FontsCache.h"
0029 #include "dot2qtconsts.h"
0030 #include "dotdefaults.h"
0031 #include "dotgraphview.h"
0032 #include "graphedge.h"
0033 #include "graphnode.h"
0034 #include "kgraphviewerlib_debug.h"
0035 
0036 #include <QAction>
0037 
0038 #include <QGraphicsSceneMouseEvent>
0039 #include <QMenu>
0040 #include <QPainter>
0041 
0042 #include <klocalizedstring.h>
0043 
0044 #include <iostream>
0045 
0046 //
0047 // CanvasEdge
0048 //
0049 
0050 namespace KGraphViewer
0051 {
0052 CanvasEdge::CanvasEdge(DotGraphView *view, GraphEdge *e, qreal scaleX, qreal scaleY, qreal xMargin, qreal yMargin, qreal gh, qreal wdhcf, qreal hdvcf, QGraphicsItem *parent)
0053     : QAbstractGraphicsShapeItem(parent)
0054     , m_scaleX(scaleX)
0055     , m_scaleY(scaleY)
0056     , m_xMargin(xMargin)
0057     , m_yMargin(yMargin)
0058     , m_gh(/*gh*/ 0)
0059     , m_wdhcf(wdhcf)
0060     , m_hdvcf(hdvcf)
0061     , m_edge(e)
0062     , m_font(nullptr)
0063     , m_view(view)
0064     , m_popup(new QMenu())
0065 {
0066     Q_UNUSED(gh);
0067     qCDebug(KGRAPHVIEWERLIB_LOG) << "edge " << edge()->fromNode()->id() << "->" << edge()->toNode()->id() << m_gh;
0068     setBoundingRegionGranularity(0.9);
0069     m_font = FontsCache::changeable().fromName(e->fontName());
0070 
0071     computeBoundingRect();
0072     //   qCDebug(KGRAPHVIEWERLIB_LOG) << "boundingRect computed: " << m_boundingRect;
0073 
0074     QString tipStr = i18n("%1 -> %2\nlabel='%3'", edge()->fromNode()->id(), edge()->toNode()->id(), e->label());
0075     setToolTip(tipStr);
0076 
0077     // the message should be given (or possible to be given) by the part user
0078     QAction *removeEdgeAction = new QAction(i18n("Remove selected edge(s)"), this);
0079     m_popup->addAction(removeEdgeAction);
0080     connect(removeEdgeAction, &QAction::triggered, this, &CanvasEdge::slotRemoveEdge);
0081 
0082     connect(e, &GraphEdge::changed, this, &CanvasEdge::modelChanged);
0083     connect(this, &CanvasEdge::selected, view, &DotGraphView::slotEdgeSelected);
0084 
0085     connect(this, &CanvasEdge::edgeContextMenuEvent, view, &DotGraphView::slotContextMenuEvent);
0086 
0087     setAcceptHoverEvents(true);
0088 
0089     qCDebug(KGRAPHVIEWERLIB_LOG) << "connect slotElementHoverEnter";
0090     connect(this, &CanvasEdge::hoverEnter, view, static_cast<void (DotGraphView::*)(CanvasEdge *)>(&DotGraphView::slotElementHoverEnter));
0091     connect(this, &CanvasEdge::hoverLeave, view, static_cast<void (DotGraphView::*)(CanvasEdge *)>(&DotGraphView::slotElementHoverLeave));
0092 }
0093 
0094 CanvasEdge::~CanvasEdge()
0095 {
0096     delete m_popup;
0097 }
0098 
0099 QRectF CanvasEdge::boundingRect() const
0100 {
0101     return m_boundingRect;
0102 }
0103 
0104 QPainterPath CanvasEdge::shape() const
0105 {
0106     //   qCDebug(KGRAPHVIEWERLIB_LOG) << edge()->fromNode()->id() << "->" << edge()->toNode()->id();
0107 
0108     if (!m_shape.isEmpty()) {
0109         return m_shape;
0110     }
0111 
0112     for (const DotRenderOp &dro : edge()->renderOperations()) {
0113         if (dro.renderop == "B") {
0114             for (int splineNum = 0; splineNum < edge()->colors().count() || (splineNum == 0 && edge()->colors().count() == 0); splineNum++) {
0115                 m_shape.addPath(pathForSpline(splineNum, dro));
0116             }
0117         }
0118     }
0119     return m_shape;
0120 }
0121 
0122 QPainterPath CanvasEdge::pathForSpline(int splineNum, const DotRenderOp &dro) const
0123 {
0124     QPolygonF points(dro.integers[0]);
0125     for (int i = 0; i < dro.integers[0]; i++) {
0126         // computing of diffX and diffY to draw parallel edges
0127         // when asked through the corresponding Graphviz feature
0128         qreal nom = (dro.integers[2 * dro.integers[0]] - dro.integers[2]);
0129         qreal denom = (dro.integers[2 * dro.integers[0] - 1] - dro.integers[1]);
0130         qreal diffX, diffY;
0131         if (nom == 0) {
0132             diffX = 0;
0133             diffY = 2 * (edge()->colors().count() / 2 - splineNum);
0134         } else if (denom == 0) {
0135             diffX = 2 * (edge()->colors().count() / 2 - splineNum);
0136             diffY = 0;
0137         } else {
0138             double pente = nom / denom;
0139             if (pente < 0) {
0140                 diffX = 2 * (edge()->colors().count() / 2 - splineNum);
0141                 diffY = edge()->colors().count() / 2 + splineNum;
0142             } else {
0143                 diffX = 2 * (edge()->colors().count() / 2 - splineNum);
0144                 diffY = 2 * (edge()->colors().count() / 2 - splineNum);
0145             }
0146         }
0147         QPointF p((dro.integers[2 * i + 1] /*%m_wdhcf*/ * m_scaleX) + m_xMargin + diffX, (m_gh - dro.integers[2 * i + 2] /*%m_hdvcf*/) * m_scaleY + m_yMargin + diffY);
0148         points[i] = p;
0149         //     qCDebug(KGRAPHVIEWERLIB_LOG) << edge()->fromNode()->id() << "->" << edge()->toNode()->id()  << p;
0150     }
0151 
0152     QPainterPath path;
0153     path.moveTo(points[0]);
0154     for (int j = 0; j < (points.size() - 1) / 3; j++) {
0155         path.cubicTo(points[3 * j + 1], points[3 * j + 1 + 1], points[3 * j + 2 + 1]);
0156     }
0157     return path;
0158 }
0159 
0160 void CanvasEdge::paint(QPainter *p, const QStyleOptionGraphicsItem *option, QWidget *widget)
0161 {
0162     //   qCDebug(KGRAPHVIEWERLIB_LOG);
0163     Q_UNUSED(option)
0164     Q_UNUSED(widget)
0165     if (m_boundingRect == QRectF()) {
0166         return;
0167     }
0168     /// computes the scaling of line width
0169     qreal widthScaleFactor = (m_scaleX + m_scaleY) / 2;
0170     if (widthScaleFactor < 1) {
0171         widthScaleFactor = 1;
0172     }
0173 
0174     if (edge()->style() == "invis") {
0175         return;
0176     }
0177     if (edge()->renderOperations().isEmpty()) {
0178         if ((edge()->fromNode()->canvasElement()) && (edge()->toNode()->canvasElement())) {
0179             p->drawLine(edge()->fromNode()->canvasElement()->boundingRect().center() + edge()->fromNode()->canvasElement()->pos(), edge()->toNode()->canvasElement()->boundingRect().center() + edge()->toNode()->canvasElement()->pos());
0180         }
0181         return;
0182     }
0183 
0184     QColor lineColor = Dot2QtConsts::componentData().qtColor(edge()->color(0));
0185     QColor backColor;
0186 
0187     QList<QPointF> allPoints;
0188 
0189     const QFont oldFont = p->font();
0190     const QPen oldPen = p->pen();
0191     const QBrush oldBrush = p->brush();
0192 
0193     for (const DotRenderOp &dro : edge()->renderOperations()) {
0194         //     qCDebug(KGRAPHVIEWERLIB_LOG) << edge()->fromNode()->id() << "->" << edge()->toNode()->id() << "renderop" << dro.renderop << "; selected:" << edge()->isSelected();
0195         if (dro.renderop == "c") {
0196             QColor c(dro.str.mid(0, 7));
0197             bool ok;
0198             c.setAlpha(255 - dro.str.mid(8).toInt(&ok, 16));
0199             lineColor = c;
0200             //       qCDebug(KGRAPHVIEWERLIB_LOG) << "c" << dro.str.mid(0,7) << lineColor;
0201         } else if (dro.renderop == "C") {
0202             QColor c(dro.str.mid(0, 7));
0203             bool ok;
0204             c.setAlpha(255 - dro.str.mid(8).toInt(&ok, 16));
0205             /*      if (m_hovered && m_view->highlighting())
0206                   {
0207                     c = c.lighter();
0208                   }*/
0209             backColor = c;
0210             //       qCDebug(KGRAPHVIEWERLIB_LOG) << "C" << dro.str.mid(0,7) << backColor;
0211         } else if (dro.renderop == "T") {
0212             const QString &str = dro.str;
0213 
0214             qreal stringWidthGoal = dro.integers[3] * m_scaleX;
0215             int fontSize = edge()->fontSize();
0216             m_font->setPointSize(fontSize);
0217             QFontMetrics fm(*m_font);
0218             while (fm.horizontalAdvance(str) > stringWidthGoal && fontSize > 1) {
0219                 fontSize--;
0220                 m_font->setPointSize(fontSize);
0221                 fm = QFontMetrics(*m_font);
0222             }
0223 
0224             p->setFont(*m_font);
0225 
0226             p->setPen(Dot2QtConsts::componentData().qtColor(edge()->fontColor()));
0227 
0228             qreal x = (m_scaleX * ((dro.integers[0]) + (((-dro.integers[2]) * (fm.horizontalAdvance(dro.str))) / 2) - ((fm.horizontalAdvance(dro.str)) / 2))) + m_xMargin;
0229             qreal y = ((m_gh - (dro.integers[1])) * m_scaleY) + m_yMargin;
0230             QPointF point(x, y);
0231             //       qCDebug(KGRAPHVIEWERLIB_LOG) << edge()->fromNode()->id() << "->" << edge()->toNode()->id() << "drawText" << edge()->fontColor() << point;
0232 
0233             p->drawText(point, str);
0234 
0235             p->setFont(oldFont);
0236             p->setPen(oldPen);
0237         } else if ((dro.renderop == "p") || (dro.renderop == "P")) {
0238             QPolygonF polygon(dro.integers[0]);
0239             for (int i = 0; i < dro.integers[0]; i++) {
0240                 QPointF point((int(dro.integers[2 * i + 1]) /*%m_wdhcf*/) * m_scaleX + m_xMargin, (int(m_gh - dro.integers[2 * i + 2]) /*%m_hdvcf*/) * m_scaleY + m_yMargin);
0241                 polygon[i] = point;
0242                 //         qCDebug(KGRAPHVIEWERLIB_LOG) << edge()->fromNode()->id() << "->" << edge()->toNode()->id()  << point;
0243                 allPoints.append(point);
0244             }
0245             if (dro.renderop == "P") {
0246                 p->setBrush(lineColor);
0247                 p->drawPolygon(polygon);
0248                 //         qCDebug(KGRAPHVIEWERLIB_LOG) << edge()->fromNode()->id() << "->" << edge()->toNode()->id() << "drawPolygon" << edge()->color(0) << polygon;
0249                 p->setBrush(oldBrush);
0250             } else {
0251                 p->setBrush(Dot2QtConsts::componentData().qtColor("white"));
0252             }
0253             QPen pen(lineColor);
0254             if (edge()->style() == "bold") {
0255                 pen.setStyle(Qt::SolidLine);
0256                 pen.setWidth((int)(2 * widthScaleFactor));
0257             } else {
0258                 pen.setWidth((int)(1 * widthScaleFactor));
0259                 pen.setStyle(Dot2QtConsts::componentData().qtPenStyle(edge()->style()));
0260             }
0261             p->setPen(pen);
0262             //       qCDebug(KGRAPHVIEWERLIB_LOG) << edge()->fromNode()->id() << "->" << edge()->toNode()->id() << "drawPolyline" << edge()->color(0) << polygon;
0263             polygon << polygon[0];
0264             p->drawPolyline(polygon);
0265             p->setPen(oldPen);
0266             p->setBrush(oldBrush);
0267         } else if ((dro.renderop == "e") || (dro.renderop == "E")) {
0268             qreal w = m_scaleX * dro.integers[2] * 2;
0269             qreal h = m_scaleY * dro.integers[3] * 2;
0270             qreal x = (m_xMargin + (dro.integers[0] /*%m_wdhcf*/) * m_scaleX) - w / 2;
0271             qreal y = ((m_gh - dro.integers[1] /*%m_hdvcf*/) * m_scaleY + m_yMargin) - h / 2;
0272             if (dro.renderop == "E") {
0273                 p->setBrush(lineColor);
0274             } else {
0275                 p->setBrush(Dot2QtConsts::componentData().qtColor("white"));
0276             }
0277             QPen pen(lineColor);
0278             if (edge()->style() == "bold") {
0279                 pen.setStyle(Qt::SolidLine);
0280                 pen.setWidth(int(2 * widthScaleFactor));
0281             } else {
0282                 pen.setWidth(int(1 * widthScaleFactor));
0283                 pen.setStyle(Dot2QtConsts::componentData().qtPenStyle(edge()->style()));
0284             }
0285             p->setPen(pen);
0286             QRectF rect(x, y, w, h);
0287             //       qCDebug(KGRAPHVIEWERLIB_LOG) << edge()->fromNode()->id() << "->" << edge()->toNode()->id() << "drawEllipse" << edge()->color(0) << rect;
0288             p->drawEllipse(rect);
0289             p->setPen(oldPen);
0290             p->setBrush(oldBrush);
0291         } else if (dro.renderop == "B") {
0292             uint lineWidth = 1;
0293             QPen pen;
0294             if (edge()->style() == "bold") {
0295                 pen.setStyle(Qt::SolidLine);
0296                 pen.setWidth(int(2 * widthScaleFactor));
0297             } else if (edge()->style() != "filled") {
0298                 pen.setStyle(Dot2QtConsts::componentData().qtPenStyle(edge()->style()));
0299             }
0300             if (edge()->style().left(12) == "setlinewidth") {
0301                 bool ok;
0302                 lineWidth = edge()->style().mid(12, edge()->style().length() - 1 - 12).toInt(&ok);
0303                 pen.setWidth(int(lineWidth * widthScaleFactor));
0304             }
0305             if (edge()->attributes().contains("penwidth")) {
0306                 bool ok;
0307                 lineWidth = edge()->attributes()["penwidth"].toInt(&ok);
0308                 pen.setWidth(int(lineWidth * widthScaleFactor));
0309             }
0310             if (edge()->attributes().contains("color")) {
0311                 qCDebug(KGRAPHVIEWERLIB_LOG) << "set edge color to " << QColor(edge()->attributes()["color"]).name();
0312                 lineColor = QColor(edge()->attributes()["color"]);
0313             }
0314             for (int splineNum = 0; splineNum < edge()->colors().count() || (splineNum == 0 && edge()->colors().count() == 0); splineNum++) {
0315                 if (splineNum != 0)
0316                     lineColor = Dot2QtConsts::componentData().qtColor(edge()->color(splineNum));
0317                 pen.setColor(lineColor);
0318                 //         p->setBrush(Dot2QtConsts::componentData().qtColor(edge()->color(0)));
0319                 p->setBrush(Qt::NoBrush);
0320                 p->setPen(pen);
0321                 //         qCDebug(KGRAPHVIEWERLIB_LOG) << edge()->fromNode()->id() << "->" << edge()->toNode()->id() << "drawPath" << edge()->color(splineNum) << points.first() << points.last();
0322                 p->drawPath(pathForSpline(splineNum, dro));
0323                 p->setPen(oldPen);
0324                 p->setBrush(oldBrush);
0325             }
0326         }
0327     }
0328     if (edge()->isSelected()) {
0329         //     qCDebug(KGRAPHVIEWERLIB_LOG) << "draw square";
0330         //     p->drawRect(m_boundingRect);
0331         qreal maxDist = 0;
0332         QPair<QPointF, QPointF> pointsPair;
0333         for (const QPointF &point1 : allPoints) {
0334             for (const QPointF &point2 : allPoints) {
0335                 if (distance(point1, point2) > maxDist) {
0336                     maxDist = distance(point1, point2);
0337                     pointsPair = qMakePair(point1, point2);
0338                 }
0339             }
0340         }
0341         if (maxDist > 0) {
0342             //         p->setBrush(Dot2QtConsts::componentData().qtColor(edge()->color(0)));
0343             p->setBrush(Qt::black);
0344             p->setPen(Qt::black);
0345             p->drawRect(QRectF(pointsPair.first - QPointF(3, 3), QSizeF(6, 6)));
0346             p->drawRect(QRectF(pointsPair.second - QPointF(3, 3), QSizeF(6, 6)));
0347             p->setBrush(oldBrush);
0348             p->setPen(oldPen);
0349         }
0350     }
0351 }
0352 
0353 void CanvasEdge::modelChanged()
0354 {
0355     //   qCDebug(KGRAPHVIEWERLIB_LOG) << edge()->fromNode()->id() << "->" << edge()->toNode()->id();
0356     prepareGeometryChange();
0357     computeBoundingRect();
0358 }
0359 
0360 void CanvasEdge::computeBoundingRect()
0361 {
0362     //   qCDebug(KGRAPHVIEWERLIB_LOG);
0363     // invalidate bounding region cache
0364     m_shape = QPainterPath();
0365     if (edge()->renderOperations().isEmpty()) {
0366         if ((edge()->fromNode()->canvasElement() == nullptr) || (edge()->toNode()->canvasElement() == nullptr) || edge()->style() == "invis") {
0367             m_boundingRect = QRectF();
0368         } else {
0369             QRectF br(edge()->fromNode()->canvasElement()->boundingRect().center() + edge()->fromNode()->canvasElement()->pos(), edge()->toNode()->canvasElement()->boundingRect().center() + edge()->toNode()->canvasElement()->pos());
0370             //       qCDebug(KGRAPHVIEWERLIB_LOG) << edge()->fromNode()->id() << "->" << edge()->toNode()->id() <<br;
0371             m_boundingRect = br;
0372         }
0373     } else {
0374         QPolygonF points;
0375         for (const DotRenderOp &dro : edge()->renderOperations()) {
0376             //       qCDebug(KGRAPHVIEWERLIB_LOG) << dro.renderop  << ", ";
0377             if ((dro.renderop != "B") && (dro.renderop != "p") && (dro.renderop != "P"))
0378                 continue;
0379             uint previousSize = points.size();
0380             points.resize(previousSize + dro.integers[0]);
0381             for (int i = 0; i < dro.integers[0]; i++) {
0382                 QPointF p(((dro.integers[2 * i + 1] /*%m_wdhcf*/) * m_scaleX) + m_xMargin, ((m_gh - dro.integers[2 * i + 2] /*%m_hdvcf*/) * m_scaleY) + m_yMargin);
0383                 points[previousSize + i] = p;
0384             }
0385         }
0386         //     qCDebug(KGRAPHVIEWERLIB_LOG) << points.size() << "points";
0387         if (points.size() == 0)
0388             return;
0389 
0390         int len = points.count();
0391         QPolygonF a = points, b = points;
0392         a.translate(-1, -1);
0393         b.translate(1, 1);
0394         a.resize(2 * len);
0395         for (int i = 0; i < len; i++) {
0396             a[len + i] = b[i];
0397         }
0398         //     qCDebug(KGRAPHVIEWERLIB_LOG) << a.size() << "points";
0399 
0400         m_boundingRect = a.boundingRect();
0401     }
0402     qCDebug(KGRAPHVIEWERLIB_LOG) << edge()->fromNode()->id() << "->" << edge()->toNode()->id() << "New bounding rect is:" << m_boundingRect;
0403 }
0404 
0405 void CanvasEdge::mousePressEvent(QGraphicsSceneMouseEvent *event)
0406 {
0407     qCDebug(KGRAPHVIEWERLIB_LOG) << event;
0408     if (m_view->isReadOnly()) {
0409         return;
0410     }
0411     if (event->button() == Qt::LeftButton) {
0412         edge()->setSelected(!edge()->isSelected());
0413         if (edge()->isSelected()) {
0414             emit(selected(this, event->modifiers()));
0415         }
0416         update();
0417     } else if (event->button() == Qt::RightButton) {
0418         if (!edge()->isSelected()) {
0419             edge()->setSelected(true);
0420             emit(selected(this, event->modifiers()));
0421             update();
0422         }
0423         qCDebug(KGRAPHVIEWERLIB_LOG) << "emiting edgeContextMenuEvent(" << m_edge->id() << "," << event->screenPos() << ")";
0424         emit(edgeContextMenuEvent(m_edge->id(), event->screenPos()));
0425         // opens the selected edge contextual menu and if necessary select the edge
0426         /*    qCDebug(KGRAPHVIEWERLIB_LOG) << "opens the contextual menu";
0427             m_popup->exec(event->screenPos());*/
0428     }
0429 }
0430 
0431 qreal CanvasEdge::distance(const QPointF &point1, const QPointF &point2)
0432 {
0433     return sqrt(pow(point1.x() - point2.x(), 2) + pow(point1.y() - point2.y(), 2));
0434 }
0435 
0436 void CanvasEdge::slotRemoveEdge()
0437 {
0438     m_view->removeSelectedElements();
0439 }
0440 
0441 void CanvasEdge::hoverEnterEvent(QGraphicsSceneHoverEvent *event)
0442 {
0443     Q_UNUSED(event)
0444     qCDebug(KGRAPHVIEWERLIB_LOG) << edge()->id();
0445     emit hoverEnter(this);
0446 }
0447 
0448 void CanvasEdge::hoverLeaveEvent(QGraphicsSceneHoverEvent *event)
0449 {
0450     Q_UNUSED(event)
0451     qCDebug(KGRAPHVIEWERLIB_LOG) << edge()->id();
0452     emit hoverLeave(this);
0453 }
0454 
0455 }
0456 
0457 #include "moc_canvasedge.cpp"