File indexing completed on 2024-05-05 04:35:18

0001 /* This file is part of the TikZKit project.
0002  *
0003  * Copyright (C) 2013 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 "NodeItem.h"
0021 #include <tikz/core/Style.h>
0022 #include "NodeText.h"
0023 #include "Painter.h"
0024 #include "AbstractShape.h"
0025 #include "DocumentPrivate.h"
0026 
0027 #include <QPainter>
0028 #include <QGraphicsScene>
0029 #include <QGraphicsView>
0030 #include <QTextLayout>
0031 #include <QGraphicsTextItem>
0032 #include <QStyle>
0033 #include <QStyleOptionGraphicsItem>
0034 #include <QPainterPath>
0035 #include <QPixmap>
0036 
0037 #include <QDebug>
0038 
0039 #include <cmath>
0040 
0041 namespace tikz {
0042 namespace ui {
0043 
0044 class NodeItemPrivate
0045 {
0046     NodeItem* q;
0047 
0048     public:
0049         NodeItemPrivate(NodeItem * nodeItem) : q(nodeItem) {}
0050 
0051         tikz::core::Node* node;
0052         NodeText* textItem;
0053         AbstractShape * shape;
0054 
0055         bool itemChangeRunning : 1;
0056         bool dirty : 1;
0057         QPainterPath shapePath;
0058         QPainterPath outlinePath;
0059 
0060     public:
0061         void updateCache()
0062         {
0063             if (!dirty) return;
0064             dirty = false;
0065 
0066             if (node->style()->shape() != shape->type()) {
0067                 delete shape;
0068                 shape = createShape(node->style()->shape(), q);
0069             }
0070 
0071             shapePath = shape->shape();
0072             outlinePath = shape->outline();
0073 
0074             q->setRotation(node->style()->rotation());
0075 
0076             q->update();
0077         }
0078 };
0079 
0080 NodeItem::NodeItem(tikz::core::Node * node, QGraphicsItem * parent)
0081     : TikzItem(parent)
0082     , d(new NodeItemPrivate(this))
0083 {
0084     d->dirty = false;
0085     d->node = node;
0086     d->shape = new AbstractShape(this);
0087     d->itemChangeRunning = false;
0088 
0089     connect(d->node, SIGNAL(changed()), this, SLOT(styleChanged()));
0090 
0091     setFlag(QGraphicsItem::ItemIsMovable, true);
0092     setFlag(QGraphicsItem::ItemSendsGeometryChanges, true);
0093     setFlag(QGraphicsItem::ItemIsSelectable, true);
0094 
0095     d->textItem = new NodeText(this);
0096     d->textItem->setPos(boundingRect().center());
0097 
0098     setCacheMode(QGraphicsItem::DeviceCoordinateCache);
0099 
0100     slotSetPos(node->pos());
0101 }
0102 
0103 NodeItem::~NodeItem()
0104 {
0105     delete d->shape;
0106     delete d;
0107 }
0108 
0109 DocumentPrivate * NodeItem::document() const
0110 {
0111     Q_ASSERT(qobject_cast<DocumentPrivate*>(d->node->document()) != nullptr);
0112     return qobject_cast<DocumentPrivate*>(d->node->document());
0113 }
0114 
0115 int NodeItem::type() const
0116 {
0117     return UserType + 2;
0118 }
0119 
0120 tikz::core::Node * NodeItem::node()
0121 {
0122     return d->node;
0123 }
0124 
0125 tikz::core::Uid NodeItem::uid() const
0126 {
0127     return d->node->uid();
0128 }
0129 
0130 tikz::core::Style* NodeItem::style() const
0131 {
0132     return d->node->style();
0133 }
0134 
0135 QStringList NodeItem::supportedAnchors() const
0136 {
0137     // make sure cache is up-to-date
0138     d->updateCache();
0139 
0140     return d->shape->supportedAnchors();
0141 }
0142 
0143 tikz::Pos NodeItem::anchor(const QString & anchor) const
0144 {
0145     // make sure cache is up-to-date
0146     d->updateCache();
0147 
0148     const QPointF p = d->shape->anchorPos(anchor);
0149     return mapToScene(p);
0150 }
0151 
0152 QPointF NodeItem::contactPoint(const QString & anchor, qreal rad) const
0153 {
0154     // make sure cache is up-to-date
0155     d->updateCache();
0156 
0157     // adapt angle to account for the self rotation of this node
0158     rad -= rotation() * M_PI / 180.0;
0159 
0160     const QPointF p = d->shape->contactPoint(anchor, rad);
0161     return mapToScene(p);
0162 }
0163 
0164 QRectF NodeItem::shapeRect() const
0165 {
0166     d->updateCache();
0167 
0168     const QRectF textRect = d->textItem->textRect();
0169     const tikz::Value innerSep = style()->innerSep();
0170 
0171     qreal w = textRect.width() + 2.0 * innerSep.toPoint();
0172     qreal h = textRect.height() + 2.0 * innerSep.toPoint();
0173 
0174     // extend rect, if minimum size is set
0175     if (w < style()->minimumWidth().toPoint()) {
0176         w = style()->minimumWidth().toPoint();
0177     }
0178     if (h < style()->minimumHeight().toPoint()) {
0179         h = style()->minimumHeight().toPoint();
0180     }
0181 
0182     QRectF rect(-w/2, -h/2, w, h);
0183     d->shape->adjustShapeRect(textRect, rect);
0184 
0185     // center shape rect at (0, 0)
0186     rect.moveCenter(QPointF(0, 0));
0187 
0188     return rect;
0189 }
0190 
0191 void NodeItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
0192 {
0193     Q_UNUSED(widget);
0194     Q_UNUSED(option);
0195 
0196     // debugging: bounding rect
0197 //     painter->drawRect(boundingRect());
0198 
0199     painter->save();
0200     painter->setRenderHints(QPainter::Antialiasing);
0201 
0202     d->updateCache();
0203 
0204     Painter p(painter, style());
0205 
0206     // fill shape
0207     p.fillPath(d->shapePath);
0208 
0209     // draw shape
0210     p.drawPath(d->shapePath);
0211 
0212     painter->restore();
0213 }
0214 
0215 QRectF NodeItem::boundingRect() const
0216 {
0217     // at this point, the bounding rect must always be up-to-date, otherwise
0218     // we have called prepareGeometryChange() without updating the cache.
0219     // And updating the cache here is too late, since prepareGeometryChange()
0220     // is always followed by a call of boundingRect().
0221     Q_ASSERT(d->dirty == false);
0222 
0223     return d->outlinePath.boundingRect();
0224 }
0225 
0226 QPainterPath NodeItem::shape() const
0227 {
0228     // make sure cache is up-to-date
0229     d->updateCache();
0230 
0231     return d->outlinePath;
0232 }
0233 
0234 QVariant NodeItem::itemChange(GraphicsItemChange change, const QVariant & value)
0235 {
0236     if (change == ItemPositionChange && scene() && !d->itemChangeRunning) {
0237         d->itemChangeRunning = true;
0238         QPointF newPos = value.toPointF();
0239         d->node->setPos(newPos);
0240         d->itemChangeRunning = false;
0241     }
0242 
0243     return QGraphicsObject::itemChange(change, value);
0244 }
0245 
0246 void NodeItem::slotSetPos(const QPointF& pos)
0247 {
0248     if (d->itemChangeRunning) return;
0249 
0250     // the tikz::core::Node position changed.
0251     // propagate this to this NodeItem::setPos().
0252     setPos(pos);
0253 }
0254 
0255 void NodeItem::styleChanged()
0256 {
0257     prepareGeometryChange();
0258     if (d->node->pos() != tikz::Pos(pos())) slotSetPos(d->node->pos());
0259 
0260     // prepareGeometryChange() will trigger a call of boundingRect(), therefore
0261     // we have to update the cache
0262     d->dirty = true;
0263     d->updateCache();
0264 
0265     Q_EMIT changed();
0266 }
0267 
0268 }
0269 }
0270 
0271 // kate: indent-width 4; replace-tabs on;