File indexing completed on 2024-06-16 05:17:22

0001 /***************************************************************************
0002  *   Copyright (C) 2003-2005 by David Saxton                               *
0003  *   david@bluehaze.org                                                    *
0004  *                                                                         *
0005  *   This program is free software; you can redistribute it and/or modify  *
0006  *   it under the terms of the GNU General Public License as published by  *
0007  *   the Free Software Foundation; either version 2 of the License, or     *
0008  *   (at your option) any later version.                                   *
0009  ***************************************************************************/
0010 
0011 #include "canvasitemparts.h"
0012 #include "cells.h"
0013 #include "component.h"
0014 #include "icndocument.h"
0015 #include "inputflownode.h"
0016 #include "itemdocumentdata.h"
0017 #include "junctionflownode.h"
0018 #include "junctionnode.h"
0019 #include "outputflownode.h"
0020 #include "pinnode.h"
0021 
0022 #include <QBitArray>
0023 #include <QPainter>
0024 
0025 #include <cmath>
0026 #include <cstdlib>
0027 
0028 #include <ktechlab_debug.h>
0029 
0030 CNItem::CNItem(ICNDocument *icnDocument, bool newItem, const QString &id)
0031     : Item(icnDocument, newItem, id)
0032     , CIWidgetMgr(icnDocument ? icnDocument->canvas() : nullptr, this)
0033     , p_icnDocument(icnDocument)
0034     , b_pointsAdded(false)
0035 {
0036     qCDebug(KTL_LOG) << " this=" << this;
0037 
0038     setZ(ICNDocument::Z::Item);
0039     setSelected(false);
0040 
0041     m_brushCol = QColor(0xf7, 0xf7, 0xff);
0042     m_selectedCol = QColor(101, 134, 192);
0043 
0044     setBrush(m_brushCol);
0045     setPen(QPen(Qt::black));
0046 }
0047 
0048 CNItem::~CNItem()
0049 {
0050     const TextMap::iterator textMapEnd = m_textMap.end();
0051     for (TextMap::iterator it = m_textMap.begin(); it != textMapEnd; ++it) {
0052         if (it.value())
0053             it.value()->setCanvas(nullptr);
0054 
0055         delete static_cast<Text *>(it.value());
0056     }
0057     m_textMap.clear();
0058 
0059     updateConnectorPoints(false);
0060 }
0061 
0062 bool CNItem::preResize(QRect sizeRect)
0063 {
0064     if ((std::abs(sizeRect.width()) < minimumSize().width()) || (std::abs(sizeRect.height()) < minimumSize().height()))
0065         return false;
0066 
0067     updateConnectorPoints(false);
0068     return true;
0069 }
0070 
0071 void CNItem::postResize()
0072 {
0073     updateAttachedPositioning();
0074 }
0075 
0076 void CNItem::setVisible(bool yes)
0077 {
0078     if (b_deleted) {
0079         Item::setVisible(false);
0080         return;
0081     }
0082 
0083     Item::setVisible(yes);
0084 
0085     const TextMap::iterator textMapEnd = m_textMap.end();
0086     for (TextMap::iterator it = m_textMap.begin(); it != textMapEnd; ++it) {
0087         it.value()->setVisible(yes);
0088     }
0089 
0090     const NodeInfoMap::iterator nodeMapEnd = m_nodeMap.end();
0091     for (NodeInfoMap::iterator it = m_nodeMap.begin(); it != nodeMapEnd; ++it) {
0092         it.value().node->setVisible(yes);
0093     }
0094 
0095     CNItem::setDrawWidgets(yes);
0096 
0097     if (!yes)
0098         updateConnectorPoints(false);
0099 }
0100 
0101 void CNItem::reparented(Item *oldParent, Item *newParent)
0102 {
0103     Item::reparented(oldParent, newParent);
0104     updateNodeLevels();
0105 }
0106 
0107 void CNItem::updateNodeLevels()
0108 {
0109     int l = level();
0110 
0111     // Tell our nodes about our level
0112     const NodeInfoMap::iterator nodeMapEnd = m_nodeMap.end();
0113     for (NodeInfoMap::iterator it = m_nodeMap.begin(); it != nodeMapEnd; ++it) {
0114         it.value().node->setLevel(l);
0115     }
0116 
0117     const ItemList::iterator end = m_children.end();
0118     for (ItemList::iterator it = m_children.begin(); it != end; ++it) {
0119         if (CNItem *cnItem = dynamic_cast<CNItem *>(static_cast<Item *>(*it)))
0120             cnItem->updateNodeLevels();
0121     }
0122 }
0123 
0124 ConnectorList CNItem::connectorList()
0125 {
0126     ConnectorList list;
0127 
0128     const NodeInfoMap::iterator nodeMapEnd = m_nodeMap.end();
0129     for (NodeInfoMap::iterator it = m_nodeMap.begin(); it != nodeMapEnd; ++it) {
0130         Node *node = p_icnDocument->nodeWithID(it.value().id);
0131         if (node) {
0132             ConnectorList nodeList = node->getAllConnectors();
0133             ConnectorList::iterator end = nodeList.end();
0134             for (ConnectorList::iterator it = nodeList.begin(); it != end; ++it) {
0135                 if (*it && !list.contains(*it)) {
0136                     list.append(*it);
0137                 }
0138             }
0139         }
0140     }
0141 
0142     return list;
0143 }
0144 
0145 void CNItem::removeItem()
0146 {
0147     if (b_deleted)
0148         return;
0149 
0150     const TextMap::iterator textMapEnd = m_textMap.end();
0151     for (TextMap::iterator it = m_textMap.begin(); it != textMapEnd; ++it)
0152         it.value()->setCanvas(nullptr);
0153 
0154     Item::removeItem();
0155     updateConnectorPoints(false);
0156 }
0157 
0158 void CNItem::restoreFromItemData(const ItemData &itemData)
0159 {
0160     Item::restoreFromItemData(itemData);
0161 
0162     updateConnectorPoints(false);
0163 
0164     {
0165         const BoolMap::const_iterator end = itemData.buttonMap.end();
0166         for (BoolMap::const_iterator it = itemData.buttonMap.begin(); it != end; ++it) {
0167             Button *b = button(it.key());
0168             if (b)
0169                 b->setState(it.value());
0170         }
0171     }
0172     {
0173         const IntMap::const_iterator end = itemData.sliderMap.end();
0174         for (IntMap::const_iterator it = itemData.sliderMap.begin(); it != end; ++it) {
0175             Slider *s = slider(it.key());
0176             if (s)
0177                 s->setValue(it.value());
0178         }
0179     }
0180 }
0181 
0182 ItemData CNItem::itemData() const
0183 {
0184     ItemData itemData = Item::itemData();
0185 
0186     const WidgetMap::const_iterator end = m_widgetMap.end();
0187     for (WidgetMap::const_iterator it = m_widgetMap.begin(); it != end; ++it) {
0188         if (Slider *slider = dynamic_cast<Slider *>(*it))
0189             itemData.sliderMap[slider->id()] = slider->value();
0190 
0191         else if (Button *button = dynamic_cast<Button *>(*it))
0192             itemData.buttonMap[button->id()] = button->state();
0193     }
0194 
0195     return itemData;
0196 }
0197 
0198 Node *CNItem::createNode(double _x, double _y, int orientation, const QString &name, uint type)
0199 {
0200     orientation %= 360;
0201     if (orientation < 0)
0202         orientation += 360;
0203 
0204     Node *node = nullptr;
0205 
0206     // TODO get rid of this switch statement...
0207     switch (type) {
0208     case Node::ec_pin:
0209         node = new PinNode(p_icnDocument, orientation, QPoint(0, 0));
0210         break;
0211     case Node::ec_junction:
0212         node = new JunctionNode(p_icnDocument, orientation, QPoint(0, 0));
0213         break;
0214     case Node::fp_junction:
0215         node = new JunctionFlowNode(p_icnDocument, orientation, QPoint(0, 0));
0216         break;
0217     case Node::fp_in:
0218         node = new InputFlowNode(p_icnDocument, orientation, QPoint(0, 0));
0219         break;
0220     case Node::fp_out:
0221         node = new OutputFlowNode(p_icnDocument, orientation, QPoint(0, 0));
0222         break;
0223     }
0224 
0225     node->setLevel(level());
0226 
0227     node->setParentItem(this);
0228     node->setChildId(name);
0229 
0230     NodeInfo info;
0231     info.id = node->id();
0232     info.node = node;
0233     info.x = _x;
0234     info.y = _y;
0235     info.orientation = orientation;
0236 
0237     m_nodeMap[name] = info;
0238 
0239     updateAttachedPositioning();
0240 
0241     return node;
0242 }
0243 
0244 bool CNItem::removeNode(const QString &name)
0245 {
0246     NodeInfoMap::iterator it = m_nodeMap.find(name);
0247     if (it == m_nodeMap.end()) {
0248         return false;
0249     }
0250     it.value().node->removeNode();
0251     p_icnDocument->flushDeleteList();
0252     m_nodeMap.erase(it);
0253     return true;
0254 }
0255 
0256 Node *CNItem::getClosestNode(const QPoint &pos)
0257 {
0258     // Work through the nodes, finding the one closest to the (x, y) position
0259     Node *shortestNode = nullptr;
0260     double shortestDistance = 1e10; // Nice large distance :-)
0261 
0262     const NodeInfoMap::iterator end = m_nodeMap.end();
0263     for (NodeInfoMap::iterator it = m_nodeMap.begin(); it != end; ++it) {
0264         Node *node = p_icnDocument->nodeWithID(it.value().id);
0265         if (node) {
0266             // Calculate the distance
0267             // Yeah, it's actually the distance squared; but it's all relative, so doesn't matter
0268             double distance = std::pow(node->x() - pos.x(), 2) + std::pow(node->y() - pos.y(), 2);
0269 
0270             if (distance < shortestDistance) {
0271                 shortestDistance = distance;
0272                 shortestNode = node;
0273             }
0274         }
0275     }
0276 
0277     return shortestNode;
0278 }
0279 
0280 void CNItem::updateAttachedPositioning()
0281 {
0282     if (b_deleted)
0283         return;
0284 
0285     // Actually, we don't do anything anymore...
0286 }
0287 
0288 void CNItem::updateZ(int baseZ)
0289 {
0290     Item::updateZ(baseZ);
0291 
0292     double _z = z();
0293 
0294     const NodeInfoMap::iterator nodeMapEnd = m_nodeMap.end();
0295     for (NodeInfoMap::iterator it = m_nodeMap.begin(); it != nodeMapEnd; ++it)
0296         it.value().node->setZ(_z + 0.5);
0297 
0298     const WidgetMap::iterator widgetMapEnd = m_widgetMap.end();
0299     for (WidgetMap::iterator it = m_widgetMap.begin(); it != widgetMapEnd; ++it)
0300         it.value()->setZ(_z + 0.5);
0301 
0302     const TextMap::iterator textMapEnd = m_textMap.end();
0303     for (TextMap::iterator it = m_textMap.begin(); it != textMapEnd; ++it)
0304         it.value()->setZ(_z + 0.5);
0305 }
0306 
0307 void CNItem::moveBy(double dx, double dy)
0308 {
0309     if (dx == 0 && dy == 0)
0310         return;
0311     updateConnectorPoints(false);
0312     Item::moveBy(dx, dy);
0313 
0314     setWidgetsPos(QPoint(int(x()), int(y())));
0315 }
0316 
0317 bool CNItem::mousePressEvent(const EventInfo &info)
0318 {
0319     bool accepted = Item::mousePressEvent(info);
0320     if (!accepted)
0321         accepted = CIWidgetMgr::mousePressEvent(info);
0322     if (accepted)
0323         setChanged();
0324     return accepted;
0325 }
0326 
0327 bool CNItem::mouseReleaseEvent(const EventInfo &info)
0328 {
0329     bool accepted = Item::mouseReleaseEvent(info);
0330     if (!accepted)
0331         accepted = CIWidgetMgr::mouseReleaseEvent(info);
0332     if (accepted)
0333         setChanged();
0334     return accepted;
0335 }
0336 
0337 bool CNItem::mouseDoubleClickEvent(const EventInfo &info)
0338 {
0339     bool accepted = Item::mouseDoubleClickEvent(info);
0340     if (!accepted)
0341         accepted = CIWidgetMgr::mouseDoubleClickEvent(info);
0342     if (accepted)
0343         setChanged();
0344     return accepted;
0345 }
0346 
0347 bool CNItem::mouseMoveEvent(const EventInfo &info)
0348 {
0349     bool accepted = Item::mouseMoveEvent(info);
0350     if (!accepted)
0351         accepted = CIWidgetMgr::mouseMoveEvent(info);
0352     if (accepted)
0353         setChanged();
0354     return accepted;
0355 }
0356 
0357 bool CNItem::wheelEvent(const EventInfo &info)
0358 {
0359     bool accepted = Item::wheelEvent(info);
0360     if (!accepted)
0361         accepted = CIWidgetMgr::wheelEvent(info);
0362     if (accepted)
0363         setChanged();
0364     return accepted;
0365 }
0366 
0367 void CNItem::enterEvent(QEvent *)
0368 {
0369     Item::enterEvent(nullptr);
0370     CIWidgetMgr::enterEvent(nullptr);
0371     setChanged();
0372 }
0373 
0374 void CNItem::leaveEvent(QEvent *)
0375 {
0376     Item::leaveEvent(nullptr);
0377     CIWidgetMgr::leaveEvent(nullptr);
0378     setChanged();
0379 }
0380 
0381 void CNItem::drawShape(QPainter &p)
0382 {
0383     if (!isVisible())
0384         return;
0385 
0386     //  initPainter(p);
0387     if (isSelected())
0388         p.setPen(m_selectedCol);
0389 
0390     p.drawPolygon(areaPoints());
0391     p.drawPolyline(areaPoints());
0392     //  deinitPainter(p);
0393 }
0394 
0395 void CNItem::initPainter(QPainter &p)
0396 {
0397     p.setRenderHint(QPainter::Antialiasing);
0398     if (isSelected())
0399         p.setPen(m_selectedCol);
0400 }
0401 
0402 void CNItem::updateConnectorPoints(bool add)
0403 {
0404     if (b_deleted || !isVisible())
0405         add = false;
0406 
0407     if (b_pointsAdded == add)
0408         return;
0409 
0410     b_pointsAdded = add;
0411 
0412     Cells *cells = p_icnDocument->cells();
0413     if (!cells)
0414         return;
0415 
0416     // Get translation matrix
0417     // Hackish...
0418     QTransform m;
0419     if (Component *c = dynamic_cast<Component *>(this))
0420         m = c->transMatrix(c->angleDegrees(), c->flipped(), int(x()), int(y()), false);
0421 
0422     // Convention used here: _UM = unmapped by both matrix and cell reference, _M = mapped
0423 
0424     const QPoint start_UM = QPoint(int(x() + offsetX()) - 8, int(y() + offsetY()) - 8);
0425     const QPoint end_UM = start_UM + QPoint(width() + 2 * 8, height() + 2 * 8);
0426 
0427     const QPoint start_M = roundDown(m.map(start_UM), 8);
0428     const QPoint end_M = roundDown(m.map(end_UM), 8);
0429 
0430     int sx_M = start_M.x();
0431     int ex_M = end_M.x();
0432 
0433     int sy_M = start_M.y();
0434     int ey_M = end_M.y();
0435 
0436     // Normalise start and end points
0437     if (sx_M > ex_M) {
0438         const int temp = sx_M;
0439         sx_M = ex_M;
0440         ex_M = temp;
0441     }
0442     if (sy_M > ey_M) {
0443         const int temp = sy_M;
0444         sy_M = ey_M;
0445         ey_M = temp;
0446     }
0447 
0448     ex_M++;
0449     ey_M++;
0450 
0451     const int mult = add ? 1 : -1;
0452 
0453     for (int x = sx_M; x < ex_M; x++) {
0454         for (int y = sy_M; y < ey_M; y++) {
0455             if (cells->haveCell(x, y)) {
0456                 if (x != sx_M && y != sy_M && x != (ex_M - 1) && y != (ey_M - 1)) {
0457                     cells->cell(x, y).CIpenalty += mult * ICNDocument::hs_item;
0458                 } else {
0459                     //                  (*cells)[x][y].CIpenalty += mult*ICNDocument::hs_item/2;
0460                     cells->cell(x, y).CIpenalty += mult * ICNDocument::hs_connector * 5;
0461                 }
0462             }
0463         }
0464     }
0465 
0466 #if 0
0467     // And subtract the positions of the node on the border
0468     NodeInfoMap::iterator end = m_nodeMap.end();
0469     for ( NodeInfoMap::iterator it = m_nodeMap.begin(); it != end; ++it )
0470     {
0471         const int x = (int)((it->second.node->x()-4)/cellSize);
0472         const int y = (int)((it->second.node->y()-4)/cellSize);
0473         if ( p_icnDocument->isValidCellReference(x,y) ) {
0474             (*cells)[x][y].CIpenalty -= mult*ICNDocument::hs_connector*5;
0475         }
0476     }
0477 #endif
0478 
0479     const TextMap::iterator textMapEnd = m_textMap.end();
0480     for (TextMap::iterator it = m_textMap.begin(); it != textMapEnd; ++it) {
0481         it.value()->updateConnectorPoints(add);
0482     }
0483     const WidgetMap::iterator widgetMapEnd = m_widgetMap.end();
0484     for (WidgetMap::iterator it = m_widgetMap.begin(); it != widgetMapEnd; ++it) {
0485         it.value()->updateConnectorPoints(add);
0486     }
0487 }
0488 
0489 Text *CNItem::addDisplayText(const QString &id, const QRect &pos, const QString &display, bool internal, int flags)
0490 {
0491     Text *text = nullptr;
0492     TextMap::iterator it = m_textMap.find(id);
0493     if (it != m_textMap.end()) {
0494         //      qCWarning(KTL_LOG) << "CNItem::addDisplayText: removing old text";
0495         delete it.value();
0496         m_textMap.erase(it);
0497     }
0498 
0499     text = new Text("", this, pos, canvas(), flags);
0500     text->setZ(z() + (internal ? 0.1 : -0.1));
0501 
0502     m_textMap[id] = text;
0503 
0504     // Calculate the correct size
0505     setDisplayText(id, display);
0506     text->show();
0507     return text;
0508 }
0509 
0510 void CNItem::setDisplayText(const QString &id, const QString &display)
0511 {
0512     TextMap::iterator it = m_textMap.find(id);
0513     if (it == m_textMap.end()) {
0514         qCCritical(KTL_LOG) << "CNItem::setDisplayText: Could not find text with id \"" << id << "\"";
0515         return;
0516     }
0517     it.value()->setText(display);
0518     updateAttachedPositioning();
0519 }
0520 
0521 void CNItem::removeDisplayText(const QString &id)
0522 {
0523     TextMap::iterator it = m_textMap.find(id);
0524     if (it == m_textMap.end()) {
0525         //      qCCritical(KTL_LOG) << "CNItem::removeDisplayText: Could not find text with id \""<<id<<"\"";
0526         return;
0527     }
0528     it.value()->updateConnectorPoints(false);
0529     delete it.value();
0530     m_textMap.erase(it);
0531 }
0532 
0533 QString CNItem::nodeId(const QString &internalNodeId)
0534 {
0535     NodeInfoMap::iterator it = m_nodeMap.find(internalNodeId);
0536     if (it == m_nodeMap.end())
0537         return "";
0538     else
0539         return it.value().id;
0540 }
0541 
0542 Node *CNItem::childNode(const QString &childId)
0543 {
0544     return p_icnDocument->nodeWithID(nodeId(childId));
0545 }
0546 
0547 NodeInfo::NodeInfo()
0548 {
0549     node = nullptr;
0550     x = 0.;
0551     y = 0.;
0552     orientation = 0;
0553 }
0554 
0555 #include "moc_cnitem.cpp"