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;