File indexing completed on 2024-12-01 11:20:30

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 "flowpart.h"
0012 #include "canvasitemparts.h"
0013 #include "connector.h"
0014 #include "flowcode.h"
0015 #include "flowcodedocument.h"
0016 #include "fpnode.h"
0017 #include "inputflownode.h"
0018 #include "itemdocument.h"
0019 #include "itemdocumentdata.h"
0020 #include "micropackage.h"
0021 #include "microsettings.h"
0022 #include "picinfo.h"
0023 #include "pinmapping.h"
0024 #include "variant.h"
0025 
0026 #include <QBitArray>
0027 #include <QBitmap>
0028 #include <QPainter>
0029 #include <QPixmap>
0030 #include <QRegExp>
0031 
0032 #include <algorithm>
0033 #include <cassert>
0034 #include <cmath>
0035 
0036 #include <ktechlab_debug.h>
0037 
0038 // Degrees per radian
0039 const double DPR = 57.29577951308232087665461840231273527024;
0040 
0041 // The following arrays of numbers represent the positions of nodes in different configurations,
0042 // with the numbers as NodeInfo::Position.
0043 
0044 int diamondNodePositioning[8][3] = {{270, 90, 0}, {270, 90, 180}, {270, 0, 90}, {270, 0, 180}, {180, 0, 90}, {180, 0, 270}, {180, 90, 0}, {180, 90, 270}};
0045 
0046 int inOutNodePositioning[8][2] = {{270, 90},
0047                                   {270, 0},
0048                                   {270, 180},
0049                                   {0, 0}, // (invalid)
0050                                   {180, 0},
0051                                   {180, 90},
0052                                   {180, 270},
0053                                   {0, 0}}; // (invalid)
0054 
0055 int inNodePositioning[4] = {270, 0, 90, 180};
0056 
0057 int outNodePositioning[4] = {90, 180, 270, 0};
0058 
0059 FlowPart::FlowPart(ICNDocument *icnDocument, bool newItem, const QString &id)
0060     : CNItem(icnDocument, newItem, id)
0061 {
0062     m_flowSymbol = FlowPart::ps_other;
0063     m_orientation = 0;
0064     m_stdInput = nullptr;
0065     m_stdOutput = nullptr;
0066     m_altOutput = nullptr;
0067 
0068     if (icnDocument) {
0069         icnDocument->registerItem(this);
0070         m_pFlowCodeDocument = dynamic_cast<FlowCodeDocument *>(icnDocument);
0071         assert(m_pFlowCodeDocument);
0072 
0073         connect(m_pFlowCodeDocument, &FlowCodeDocument::picTypeChanged, this, &FlowPart::slotUpdateFlowPartVariables);
0074         connect(m_pFlowCodeDocument, &FlowCodeDocument::pinMappingsChangedFlowCode, this, &FlowPart::slotUpdateFlowPartVariables);
0075     }
0076 }
0077 
0078 FlowPart::~FlowPart()
0079 {
0080     // We have to check view, as if the item is deleted before the CNItem constructor
0081     // is called, then there will be no view
0082     if (m_pFlowCodeDocument) {
0083         const VariantDataMap::iterator end = m_variantData.end();
0084         for (VariantDataMap::iterator it = m_variantData.begin(); it != end; ++it) {
0085             Variant *v = it.value();
0086             if (v)
0087                 m_pFlowCodeDocument->varNameChanged("", v->value().toString());
0088         }
0089     }
0090 }
0091 
0092 void FlowPart::setCaption(const QString &caption)
0093 {
0094     if (m_flowSymbol == FlowPart::ps_other) {
0095         m_caption = caption;
0096         return;
0097     }
0098 
0099     // 2016.05.03 - do not use temporary widget for getting font metrics
0100     //  QWidget *w = new QWidget();
0101     //  //QPainter p(w);
0102     //     QPainter p;
0103     //     const bool isSuccess = p.begin(w);
0104     //     if (!isSuccess) {
0105     //         qCWarning(KTL_LOG) << " painter not active";
0106     //     }
0107     //  p.setFont( font() );
0108     //  const int text_width = p.boundingRect( boundingRect(), (Qt::SingleLine | Qt::AlignHCenter | Qt::AlignVCenter), caption ).width();
0109     //  p.end();
0110     //  delete w;
0111 
0112     QFontMetrics fontMetrics(font());
0113     const int text_width = fontMetrics.boundingRect(boundingRect(), (Qt::TextSingleLine | Qt::AlignHCenter | Qt::AlignVCenter), caption).width();
0114 
0115     int width = std::max((int(text_width / 16)) * 16, 48);
0116 
0117     switch (m_flowSymbol) {
0118     case FlowPart::ps_call:
0119         width += 48;
0120         break;
0121     case FlowPart::ps_io:
0122     case FlowPart::ps_round:
0123         width += 32;
0124         break;
0125     case FlowPart::ps_decision:
0126         width += 64;
0127         break;
0128     case FlowPart::ps_process:
0129     default:
0130         width += 32;
0131         break;
0132     }
0133 
0134     bool hasSideConnectors = m_flowSymbol == FlowPart::ps_decision;
0135     if (hasSideConnectors && (width != this->width()))
0136         p_icnDocument->requestRerouteInvalidatedConnectors();
0137 
0138     initSymbol(m_flowSymbol, width);
0139     m_caption = caption;
0140 }
0141 void FlowPart::postResize()
0142 {
0143     updateNodePositions();
0144     CNItem::postResize();
0145 }
0146 
0147 void FlowPart::createStdInput()
0148 {
0149     m_stdInput = static_cast<FPNode *>(createNode(0, 0, 270, "stdinput", Node::fp_in));
0150     updateNodePositions();
0151 }
0152 void FlowPart::createStdOutput()
0153 {
0154     m_stdOutput = static_cast<FPNode *>(createNode(0, 0, 90, "stdoutput", Node::fp_out));
0155     updateNodePositions();
0156 }
0157 void FlowPart::createAltOutput()
0158 {
0159     m_altOutput = static_cast<FPNode *>(createNode(0, 0, 0, "altoutput", Node::fp_out));
0160     updateNodePositions();
0161 }
0162 
0163 void FlowPart::initSymbol(FlowPart::FlowSymbol symbol, int width)
0164 {
0165     m_flowSymbol = symbol;
0166 
0167     switch (symbol) {
0168     case FlowPart::ps_other:
0169         return;
0170     case FlowPart::ps_call:
0171     case FlowPart::ps_process:
0172         setItemPoints(QRect(-width / 2, -16, width, 24));
0173         break;
0174     case FlowPart::ps_io: {
0175         // define parallelogram shape
0176         QPolygon pa(4);
0177         pa[0] = QPoint(-(width - 10) / 2, -16);
0178         pa[1] = QPoint(width / 2, -16);
0179         pa[2] = QPoint((width - 10) / 2, 8);
0180         pa[3] = QPoint(-width / 2, 8);
0181         setItemPoints(pa);
0182         break;
0183     }
0184     case FlowPart::ps_round: {
0185         // define rounded rectangles as two semicricles with RP_NUM/2 points with gap inbetween
0186         // These points are not used for drawing; merely for passing to qcanvaspolygonitem for collision detection
0187         // If there is a better way for a rounder rectangle + collision detection, please let me know...
0188 
0189         int halfHeight = 12;
0190 
0191         // Draw semicircle
0192         double x;
0193         const int RP_NUM = 48;
0194         QPolygon pa(RP_NUM);
0195         int point = 0;
0196         for (double y = -1.0; y <= 1.0; y += 4.0 / (RP_NUM - 2)) {
0197             x = sqrt(1 - y * y) * halfHeight;
0198             pa[point] = QPoint(int(width + x) - halfHeight, int(halfHeight * y));
0199             pa[RP_NUM - 1 - point] = QPoint(int(halfHeight - x), int(halfHeight * y));
0200             point++;
0201         }
0202 
0203         pa.translate(-width / 2, 4);
0204         setItemPoints(pa);
0205         break;
0206     }
0207 
0208     case FlowPart::ps_decision: {
0209         // define rhombus
0210         QPolygon pa(6);
0211         pa[0] = QPoint(0, -24);
0212         pa[1] = QPoint(width / 2, -6);
0213         pa[2] = QPoint(width / 2, 6);
0214         pa[3] = QPoint(0, 24);
0215         pa[4] = QPoint(-width / 2, 6);
0216         pa[5] = QPoint(-width / 2, -6);
0217         setItemPoints(pa);
0218         break;
0219     }
0220     default:
0221         qCCritical(KTL_LOG) << "Unknown flowSymbol: " << symbol;
0222     }
0223 }
0224 
0225 void FlowPart::drawShape(QPainter &p)
0226 {
0227     initPainter(p);
0228 
0229     const double _x = int(x() + offsetX());
0230     const double _y = int(y() + offsetY());
0231     const double w = width();
0232     double h = height();
0233 
0234     switch (m_flowSymbol) {
0235     case FlowPart::ps_other:
0236         CNItem::drawShape(p);
0237         break;
0238     case FlowPart::ps_io: {
0239         h--;
0240         double roundSize = 8;
0241         double slantIndent = 5;
0242 
0243         //      CNItem::drawShape(p);
0244         double inner = std::atan(h / slantIndent);
0245         double outer = M_PI - inner;
0246 
0247         int inner16 = int(16 * inner * DPR);
0248         int outer16 = int(16 * outer * DPR);
0249 
0250         p.save();
0251         p.setPen(Qt::NoPen);
0252         p.drawPolygon(areaPoints());
0253         p.restore();
0254 
0255         p.drawLine(int(_x + slantIndent + roundSize / 2), int(_y), int(_x + w - roundSize / 2), int(_y));
0256         p.drawLine(int(_x + w - slantIndent - roundSize / 2), int(_y + h), int(_x + roundSize / 2), int(_y + h));
0257         p.drawLine(
0258             int(_x + w + (std::sin(outer) - 1) * roundSize / 2), int(_y + (1 - std::cos(outer)) * roundSize / 2), int(_x + w - slantIndent + (std::sin(inner) - 1) * roundSize / 2), int(_y + h + (std::cos(inner) - 1) * roundSize / 2));
0259         p.drawLine(int(_x + (1 - std::sin(outer)) * roundSize / 2), int(_y + h + (std::cos(outer) - 1) * roundSize / 2), int(_x + slantIndent + (1 - std::sin(inner)) * roundSize / 2), int(_y + (1 - std::cos(inner)) * roundSize / 2));
0260         p.drawArc(int(_x + slantIndent), int(_y), int(roundSize), int(roundSize), 90 * 16, inner16);
0261         p.drawArc(int(_x + w - roundSize), int(_y), int(roundSize), int(roundSize), 270 * 16 + inner16, outer16);
0262         p.drawArc(int(_x - slantIndent + w - roundSize), int(_y + h - roundSize), int(roundSize), int(roundSize), 270 * 16, inner16);
0263         p.drawArc(int(_x), int(_y + h - roundSize), int(roundSize), int(roundSize), 90 * 16 + inner16, outer16);
0264         break;
0265     }
0266     case FlowPart::ps_decision:
0267         // TODO Make the shape nice and pretty with rounded corners
0268         CNItem::drawShape(p);
0269         break;
0270     case FlowPart::ps_call:
0271         p.drawRoundedRect(int(_x), int(_y), int(w), int(h + 1), int(1000. / w), int(1000. / h), Qt::RelativeSize);
0272         p.drawLine(int(_x + 8), int(_y), int(_x + 8), int(_y + h));
0273         p.drawLine(int(_x + w - 8), int(_y), int(_x + w - 8), int(_y + h));
0274         break;
0275     case FlowPart::ps_process:
0276         p.drawRoundedRect(int(_x), int(_y), int(w), int(h + 1), int(1000. / w), int(1000. / h), Qt::RelativeSize);
0277         break;
0278     case FlowPart::ps_round:
0279         p.drawRoundedRect(int(_x), int(_y), int(w), int(h + 1), 30, 100, Qt::RelativeSize);
0280         break;
0281     }
0282 
0283     p.setPen(Qt::black);
0284     p.setFont(font());
0285     p.drawText(boundingRect(), (Qt::TextWordWrap | Qt::AlignHCenter | Qt::AlignVCenter), m_caption);
0286 }
0287 
0288 QString FlowPart::gotoCode(const QString &internalNodeId)
0289 {
0290     FlowPart *end = outputPart(internalNodeId);
0291     if (!end)
0292         return "";
0293     return "goto " + end->id();
0294 }
0295 
0296 FlowPart *FlowPart::outputPart(const QString &internalNodeId)
0297 {
0298     Node *node = p_icnDocument->nodeWithID(nodeId(internalNodeId));
0299 
0300     FPNode *fpnode = dynamic_cast<FPNode *>(node);
0301     // FIXME dynamic_cast used to replace fpnode::type() call
0302     if (!fpnode || (dynamic_cast<InputFlowNode *>(fpnode) != nullptr))
0303         // if ( !fpnode || fpnode->type() == Node::fp_in )
0304         return nullptr;
0305 
0306     return fpnode->outputFlowPart();
0307 }
0308 
0309 FlowPartList FlowPart::inputParts(const QString &id)
0310 {
0311     Node *node = p_icnDocument->nodeWithID(id);
0312 
0313     if (FPNode *fpNode = dynamic_cast<FPNode *>(node))
0314         return fpNode->inputFlowParts();
0315 
0316     return FlowPartList();
0317 }
0318 
0319 FlowPartList FlowPart::inputParts()
0320 {
0321     FlowPartList list;
0322 
0323     const NodeInfoMap::iterator nEnd = m_nodeMap.end();
0324     for (NodeInfoMap::iterator it = m_nodeMap.begin(); it != nEnd; ++it) {
0325         Node *node = p_icnDocument->nodeWithID(it.value().id);
0326         FlowPartList newList;
0327 
0328         if (FPNode *fpNode = dynamic_cast<FPNode *>(node))
0329             newList = fpNode->inputFlowParts();
0330 
0331         const FlowPartList::iterator nlEnd = newList.end();
0332         for (FlowPartList::iterator it = newList.begin(); it != nlEnd; ++it) {
0333             if (*it)
0334                 list.append(*it);
0335         }
0336     }
0337 
0338     return list;
0339 }
0340 
0341 FlowPartList FlowPart::outputParts()
0342 {
0343     FlowPartList list;
0344 
0345     const NodeInfoMap::iterator end = m_nodeMap.end();
0346     for (NodeInfoMap::iterator it = m_nodeMap.begin(); it != end; ++it) {
0347         FlowPart *part = outputPart(it.key());
0348         if (part)
0349             list.append(part);
0350     }
0351 
0352     return list;
0353 }
0354 
0355 FlowPart *FlowPart::endPart(QStringList ids, FlowPartList *previousParts)
0356 {
0357     if (ids.empty()) {
0358         const NodeInfoMap::iterator end = m_nodeMap.end();
0359         for (NodeInfoMap::iterator it = m_nodeMap.begin(); it != end; ++it) {
0360             ids.append(it.key());
0361         }
0362         filterEndPartIDs(&ids);
0363     }
0364 
0365     const bool createdList = (!previousParts);
0366     if (createdList) {
0367         previousParts = new FlowPartList;
0368     } else if (previousParts->contains(this)) {
0369         return nullptr;
0370     }
0371 
0372     previousParts->append(this);
0373 
0374     if (ids.empty()) {
0375         return nullptr;
0376     }
0377 
0378     if (ids.size() == 1) {
0379         return outputPart(*(ids.begin()));
0380     }
0381 
0382     typedef QList<FlowPartList> ValidPartsList;
0383     ValidPartsList validPartsList;
0384 
0385     const QStringList::iterator idsEnd = ids.end();
0386     for (QStringList::iterator it = ids.begin(); it != idsEnd; ++it) {
0387         //int prevLevel = level();
0388         FlowPartList validParts;
0389         FlowPart *part = outputPart(*it);
0390         while (part) {
0391             if (!validParts.contains(part)) {
0392                 validParts.append(part);
0393                 //              if ( part->level() >= level() ) {
0394                 //const int _l = part->level();
0395                 part = part->endPart(QStringList(), previousParts);
0396                 //prevLevel = _l;
0397                 //              } else {
0398                 //                  part = nullptr;
0399                 //              }
0400             } else {
0401                 part = nullptr;
0402             }
0403         }
0404         if (!validParts.empty()) {
0405             validPartsList.append(validParts);
0406         }
0407     }
0408 
0409     if (createdList) {
0410         delete previousParts;
0411         previousParts = nullptr;
0412     }
0413 
0414     if (validPartsList.empty())
0415         return nullptr;
0416 
0417     FlowPartList firstList = *(validPartsList.begin());
0418     const FlowPartList::iterator flEnd = firstList.end();
0419     const ValidPartsList::iterator vplEnd = validPartsList.end();
0420     for (FlowPartList::iterator it = firstList.begin(); it != flEnd; ++it) {
0421         bool ok = true;
0422         for (ValidPartsList::iterator vplit = validPartsList.begin(); vplit != vplEnd; ++vplit) {
0423             if (!(*vplit).contains(*it))
0424                 ok = false;
0425         }
0426         if (ok)
0427             return *it;
0428     }
0429 
0430     return nullptr;
0431 }
0432 
0433 void FlowPart::handleIfElse(FlowCode *code, const QString &case1Statement, const QString &case2Statement, const QString &case1, const QString &case2)
0434 {
0435     if (!code)
0436         return;
0437 
0438     FlowPart *stop = nullptr;
0439     FlowPart *part1 = outputPart(case1);
0440     FlowPart *part2 = outputPart(case2);
0441 
0442     if (part1 && part2)
0443         stop = endPart((QStringList(case1) << case2));
0444 
0445     if ((!part1 && !part2) || (part1 == stop && part2 == stop))
0446         return;
0447 
0448     code->addStopPart(stop);
0449 
0450     if (part1 && part1 != stop && code->isValidBranch(part1)) {
0451         // Use the case1 statement
0452         code->addCode("if " + case1Statement + " then " + "\n{");
0453         code->addCodeBranch(part1);
0454         code->addCode("}");
0455 
0456         if (part2 && part2 != stop && code->isValidBranch(part2)) {
0457             code->addCode("else\n{");
0458             code->addCodeBranch(part2);
0459             code->addCode("}");
0460         }
0461     } else if (code->isValidBranch(part2)) {
0462         // Use the case2 statement
0463         code->addCode("if " + case2Statement + " then " + "\n{");
0464         code->addCodeBranch(part2);
0465         code->addCode("}");
0466     }
0467 
0468     code->removeStopPart(stop);
0469     code->addCodeBranch(stop);
0470 }
0471 
0472 Variant *FlowPart::createProperty(const QString &id, Variant::Type::Value type)
0473 {
0474     if (type != Variant::Type::Port && type != Variant::Type::Pin && type != Variant::Type::VarName && type != Variant::Type::SevenSegment && type != Variant::Type::KeyPad)
0475         return CNItem::createProperty(id, type);
0476 
0477     Variant *v = createProperty(id, Variant::Type::String);
0478     v->setType(type);
0479 
0480     if (type == Variant::Type::VarName) {
0481         if (m_pFlowCodeDocument) {
0482             if (MicroSettings *settings = m_pFlowCodeDocument->microSettings())
0483                 v->setAllowed(settings->variableNames());
0484         }
0485         connect(property(id), qOverload<QVariant, QVariant>(&Property::valueChanged), this, &FlowPart::varNameChanged);
0486     } else
0487         slotUpdateFlowPartVariables();
0488 
0489     return v;
0490 }
0491 
0492 void FlowPart::slotUpdateFlowPartVariables()
0493 {
0494     if (!m_pFlowCodeDocument)
0495         return;
0496 
0497     MicroSettings *s = m_pFlowCodeDocument->microSettings();
0498     if (!s)
0499         return;
0500 
0501     const PinMappingMap pinMappings = s->pinMappings();
0502     QStringList sevenSegMaps;
0503     QStringList keyPadMaps;
0504 
0505     PinMappingMap::const_iterator pEnd = pinMappings.end();
0506     for (PinMappingMap::const_iterator it = pinMappings.begin(); it != pEnd; ++it) {
0507         switch (it.value().type()) {
0508         case PinMapping::SevenSegment:
0509             sevenSegMaps << it.key();
0510             break;
0511 
0512         case PinMapping::Keypad_4x3:
0513         case PinMapping::Keypad_4x4:
0514             keyPadMaps << it.key();
0515             break;
0516 
0517         case PinMapping::Invalid:
0518             break;
0519         }
0520     }
0521 
0522     QStringList ports = s->microInfo()->package()->portNames();
0523     ports.sort();
0524 
0525     QStringList pins = s->microInfo()->package()->pinIDs(PicPin::type_bidir | PicPin::type_input | PicPin::type_open);
0526     pins.sort();
0527 
0528     const VariantDataMap::iterator vEnd = m_variantData.end();
0529     for (VariantDataMap::iterator it = m_variantData.begin(); it != vEnd; ++it) {
0530         Variant *v = it.value();
0531         if (!v)
0532             continue;
0533 
0534         if (v->type() == Variant::Type::Port)
0535             v->setAllowed(ports);
0536         else if (v->type() == Variant::Type::Pin)
0537             v->setAllowed(pins);
0538         else if (v->type() == Variant::Type::SevenSegment) {
0539             v->setAllowed(sevenSegMaps);
0540             if (!sevenSegMaps.isEmpty() && !sevenSegMaps.contains(v->value().toString()))
0541                 v->setValue(sevenSegMaps.first());
0542         } else if (v->type() == Variant::Type::KeyPad) {
0543             v->setAllowed(keyPadMaps);
0544             if (!keyPadMaps.isEmpty() && !keyPadMaps.contains(v->value().toString()))
0545                 v->setValue(keyPadMaps.first());
0546         }
0547     }
0548 }
0549 
0550 void FlowPart::updateVarNames()
0551 {
0552     if (!m_pFlowCodeDocument)
0553         return;
0554 
0555     MicroSettings *s = m_pFlowCodeDocument->microSettings();
0556     if (!s)
0557         return;
0558 
0559     const QStringList names = s->variableNames();
0560     const VariantDataMap::iterator end = m_variantData.end();
0561     for (VariantDataMap::iterator it = m_variantData.begin(); it != end; ++it) {
0562         Variant *v = it.value();
0563         if (v && v->type() == Variant::Type::VarName)
0564             v->setAllowed(names);
0565     }
0566 }
0567 
0568 void FlowPart::varNameChanged(QVariant newValue, QVariant oldValue)
0569 {
0570     if (!m_pFlowCodeDocument)
0571         return;
0572     m_pFlowCodeDocument->varNameChanged(newValue.toString(), oldValue.toString());
0573 }
0574 
0575 inline int nodeDirToPos(int dir)
0576 {
0577     switch (dir) {
0578     case 0:
0579         return 0;
0580     case 270:
0581         return 1;
0582     case 180:
0583         return 2;
0584     case 90:
0585         return 3;
0586     }
0587     return 0;
0588 }
0589 
0590 void FlowPart::updateAttachedPositioning()
0591 {
0592     if (b_deleted)
0593         return;
0594 
0595     // BEGIN Rearrange text if appropriate
0596     const QRect textPos[4] = {QRect(offsetX() + width(), 6, 40, 16), QRect(0, offsetY() - 16, 40, 16), QRect(offsetX() - 40, 6, 40, 16), QRect(0, offsetY() + height(), 40, 16)};
0597 
0598     NodeInfo *stdOutputInfo = m_stdOutput ? &m_nodeMap["stdoutput"] : nullptr;
0599     NodeInfo *altOutputInfo = m_altOutput ? &m_nodeMap["altoutput"] : nullptr;
0600 
0601     Text *outputTrueText = m_textMap.contains("output_true") ? m_textMap["output_true"] : nullptr;
0602     Text *outputFalseText = m_textMap.contains("output_false") ? m_textMap["output_false"] : nullptr;
0603 
0604     if (stdOutputInfo && outputTrueText)
0605         outputTrueText->setOriginalRect(textPos[nodeDirToPos(stdOutputInfo->orientation)]);
0606 
0607     if (altOutputInfo && outputFalseText)
0608         outputFalseText->setOriginalRect(textPos[nodeDirToPos(altOutputInfo->orientation)]);
0609 
0610     const TextMap::iterator textMapEnd = m_textMap.end();
0611     for (TextMap::iterator it = m_textMap.begin(); it != textMapEnd; ++it) {
0612         QRect pos = it.value()->recommendedRect();
0613         it.value()->move(pos.x() + x(), pos.y() + y());
0614         it.value()->setGuiPartSize(pos.width(), pos.height());
0615     }
0616     // END Rearrange text if appropriate
0617 
0618     const NodeInfoMap::iterator end = m_nodeMap.end();
0619     for (NodeInfoMap::iterator it = m_nodeMap.begin(); it != end; ++it) {
0620         if (!it.value().node) {
0621             qCCritical(KTL_LOG) << "Node in nodemap is null";
0622             continue;
0623         }
0624 
0625         double nx = it.value().x;
0626         double ny = it.value().y;
0627 
0628 #define round_8(x) (((x) > 0) ? int(((x) + 4) / 8) * 8 : int(((x)-4) / 8) * 8)
0629         nx = round_8(nx);
0630         ny = round_8(ny);
0631 #undef round_8
0632 
0633         it.value().node->move(int(nx + x()), int(ny + y()));
0634         it.value().node->setOrientation(it.value().orientation);
0635     }
0636 }
0637 
0638 ItemData FlowPart::itemData() const
0639 {
0640     ItemData itemData = CNItem::itemData();
0641     itemData.orientation = m_orientation;
0642     return itemData;
0643 }
0644 
0645 void FlowPart::restoreFromItemData(const ItemData &itemData)
0646 {
0647     CNItem::restoreFromItemData(itemData);
0648     if (itemData.orientation >= 0)
0649         setOrientation(uint(itemData.orientation));
0650 }
0651 
0652 void FlowPart::updateNodePositions()
0653 {
0654     if (m_orientation > 7) {
0655         qCWarning(KTL_LOG) << "Invalid orientation: " << m_orientation;
0656         return;
0657     }
0658 
0659     NodeInfo *stdInputInfo = m_stdInput ? &m_nodeMap["stdinput"] : nullptr;
0660     NodeInfo *stdOutputInfo = m_stdOutput ? &m_nodeMap["stdoutput"] : nullptr;
0661     NodeInfo *altOutputInfo = m_altOutput ? &m_nodeMap["altoutput"] : nullptr;
0662 
0663     if (m_stdInput && m_stdOutput && m_altOutput) {
0664         stdInputInfo->orientation = diamondNodePositioning[m_orientation][0];
0665         stdOutputInfo->orientation = diamondNodePositioning[m_orientation][1];
0666         altOutputInfo->orientation = diamondNodePositioning[m_orientation][2];
0667     } else if (m_stdInput && m_stdOutput) {
0668         stdInputInfo->orientation = inOutNodePositioning[m_orientation][0];
0669         stdOutputInfo->orientation = inOutNodePositioning[m_orientation][1];
0670     } else if (m_orientation < 4) {
0671         if (stdInputInfo)
0672             stdInputInfo->orientation = inNodePositioning[m_orientation];
0673         else if (stdOutputInfo)
0674             stdOutputInfo->orientation = outNodePositioning[m_orientation];
0675     } else {
0676         qCWarning(KTL_LOG) << "Invalid orientation: " << m_orientation;
0677         return;
0678     }
0679 
0680     const NodeInfoMap::iterator end = m_nodeMap.end();
0681     for (NodeInfoMap::iterator it = m_nodeMap.begin(); it != end; ++it) {
0682         if (!it.value().node)
0683             qCCritical(KTL_LOG) << "Node in nodemap is null";
0684         else {
0685             switch (it.value().orientation) {
0686             case 0:
0687                 it.value().x = offsetX() + width() + 8;
0688                 it.value().y = 0;
0689                 break;
0690             case 270:
0691                 it.value().x = 0;
0692                 it.value().y = offsetY() - 8;
0693                 break;
0694             case 180:
0695                 it.value().x = offsetX() - 8;
0696                 it.value().y = 0;
0697                 break;
0698             case 90:
0699                 it.value().x = 0;
0700                 it.value().y = offsetY() + height() + 8;
0701                 ;
0702                 break;
0703             }
0704         }
0705     }
0706 
0707     updateAttachedPositioning();
0708 }
0709 
0710 void FlowPart::setOrientation(uint orientation)
0711 {
0712     if (orientation == m_orientation)
0713         return;
0714 
0715     m_orientation = orientation;
0716     updateNodePositions();
0717     p_icnDocument->requestRerouteInvalidatedConnectors();
0718 }
0719 
0720 uint FlowPart::allowedOrientations() const
0721 {
0722     // The bit positions shown here represent whether or not that orientation is allowed, the orientation being
0723     // what is displayed in the i'th position (0 to 3 on top, 4 to 7 on bottom) of orientation widget
0724 
0725     if (m_stdInput && m_stdOutput && m_altOutput)
0726         return 255;
0727 
0728     if (m_stdInput && m_stdOutput)
0729         return 119;
0730 
0731     if (m_stdInput || m_stdOutput)
0732         return 15;
0733 
0734     return 0;
0735 }
0736 
0737 void FlowPart::orientationPixmap(uint orientation, QPixmap &pm) const
0738 {
0739     const QSize size = pm.size();
0740 
0741     if (!(allowedOrientations() & (1 << orientation))) {
0742         qCWarning(KTL_LOG) << "Requesting invalid orientation of " << orientation;
0743         return;
0744     }
0745 
0746     QBitmap mask(50, 50);
0747     // QPainter maskPainter(&mask); // 2016.05.03 - initialize painter explicitly
0748     QPainter maskPainter;
0749     {
0750         const bool isSuccess = maskPainter.begin(&mask);
0751         if (!isSuccess) {
0752             qCWarning(KTL_LOG) << " painter not active";
0753         }
0754     }
0755 
0756     mask.fill(Qt::color0);
0757     maskPainter.setBrush(Qt::color1);
0758     maskPainter.setPen(Qt::color1);
0759 
0760     // BEGIN painter on pm
0761     {
0762         // QPainter p(&pm); // 2016.05.03 - explicitly initialize painter
0763         QPainter p;
0764         const bool isBeginSuccess = p.begin(&pm);
0765         if (!isBeginSuccess) {
0766             qCWarning(KTL_LOG) << " painter not active";
0767         }
0768         p.setBrush(m_brushCol);
0769         p.setPen(Qt::black);
0770 
0771         // In order: right corner, top corner, left corner, bottom corner
0772 
0773         QPoint c[4] = {QPoint(int(0.7 * size.width()), int(0.5 * size.height())),
0774                        QPoint(int(0.5 * size.width()), int(0.4 * size.height())),
0775                        QPoint(int(0.3 * size.width()), int(0.5 * size.height())),
0776                        QPoint(int(0.5 * size.width()), int(0.6 * size.height()))};
0777 
0778         QPoint d[4];
0779         d[0] = c[0] + QPoint(7, 0);
0780         d[1] = c[1] + QPoint(0, -7);
0781         d[2] = c[2] + QPoint(-7, 0);
0782         d[3] = c[3] + QPoint(0, 7);
0783 
0784         if (m_stdInput && m_stdOutput && m_altOutput) {
0785             // BEGIN Draw diamond outline
0786             QPolygon diamond(4);
0787             for (uint i = 0; i < 4; ++i)
0788                 diamond[i] = c[i];
0789 
0790             p.drawPolygon(diamond);
0791             maskPainter.drawPolygon(diamond);
0792             // END Draw diamond outline
0793 
0794             // BEGIN Draw input
0795             int pos0 = nodeDirToPos(diamondNodePositioning[orientation][0]);
0796             p.drawLine(c[pos0], d[pos0]);
0797             maskPainter.drawLine(c[pos0], d[pos0]);
0798             // END Draw input
0799 
0800             // BEGIN Draw "true" output as a tick
0801             QPolygon tick(4);
0802             tick[0] = QPoint(-3, 0);
0803             tick[1] = QPoint(0, 2);
0804             tick[2] = QPoint(0, 2);
0805             tick[3] = QPoint(4, -2);
0806 
0807             int pos1 = nodeDirToPos(diamondNodePositioning[orientation][1]);
0808             tick.translate(d[pos1].x(), d[pos1].y());
0809             p.drawLines(tick);
0810             maskPainter.drawLines(tick);
0811             // END Draw "true" output as a tick
0812 
0813             // BEGIN Draw "false" output as a cross
0814             QPolygon cross(4);
0815             cross[0] = QPoint(-2, -2);
0816             cross[1] = QPoint(2, 2);
0817             cross[2] = QPoint(-2, 2);
0818             cross[3] = QPoint(2, -2);
0819 
0820             int pos2 = nodeDirToPos(diamondNodePositioning[orientation][2]);
0821             cross.translate(d[pos2].x(), d[pos2].y());
0822             p.drawLines(cross);
0823             maskPainter.drawLines(cross);
0824             // END Draw "false" output as a cross
0825         } else if (m_stdInput || m_stdOutput) {
0826             p.drawRoundedRect(int(0.3 * size.width()), int(0.4 * size.height()), int(0.4 * size.width()), int(0.2 * size.height()), 25, 25, Qt::RelativeSize);
0827             maskPainter.drawRoundedRect(int(0.3 * size.width()), int(0.4 * size.height()), int(0.4 * size.width()), int(0.2 * size.height()), 25, 25, Qt::RelativeSize);
0828 
0829             int hal = 5; // half arrow length
0830             int haw = 3; // half arrow width
0831 
0832             QPoint arrows[4][6] = {{QPoint(hal, 0), QPoint(0, -haw), QPoint(hal, 0), QPoint(-hal, 0), QPoint(hal, 0), QPoint(0, haw)},
0833 
0834                                    {QPoint(0, -hal), QPoint(-haw, 0), QPoint(0, -hal), QPoint(0, hal), QPoint(0, -hal), QPoint(haw, 0)},
0835 
0836                                    {QPoint(-hal, 0), QPoint(0, -haw), QPoint(-hal, 0), QPoint(hal, 0), QPoint(-hal, 0), QPoint(0, haw)},
0837 
0838                                    {QPoint(0, hal), QPoint(-haw, 0), QPoint(0, hal), QPoint(0, -hal), QPoint(0, hal), QPoint(haw, 0)}};
0839 
0840             int inPos = -1;
0841             int outPos = -1;
0842 
0843             if (m_stdInput && m_stdOutput) {
0844                 inPos = nodeDirToPos(inOutNodePositioning[orientation][0]);
0845                 outPos = nodeDirToPos(inOutNodePositioning[orientation][1]);
0846             } else if (m_stdInput) {
0847                 inPos = nodeDirToPos(inNodePositioning[orientation]);
0848             } else if (m_stdOutput) {
0849                 outPos = nodeDirToPos(outNodePositioning[orientation]);
0850             }
0851 
0852             if (inPos != -1) {
0853                 QPolygon inArrow(6);
0854                 for (int i = 0; i < 6; ++i) {
0855                     inArrow[i] = arrows[(inPos + 2) % 4][i];
0856                 }
0857                 inArrow.translate(d[inPos].x(), d[inPos].y());
0858                 p.drawPolygon(inArrow);
0859                 maskPainter.drawPolygon(inArrow);
0860             }
0861 
0862             if (outPos != -1) {
0863                 QPolygon outArrow(6);
0864                 for (int i = 0; i < 6; ++i) {
0865                     outArrow[i] = arrows[outPos][i];
0866                 }
0867                 outArrow.translate(d[outPos].x(), d[outPos].y());
0868                 p.drawPolygon(outArrow);
0869                 maskPainter.drawPolygon(outArrow);
0870             }
0871         }
0872     }
0873     // END painter on pm
0874     pm.setMask(mask); // pm needs not to have active painters on it
0875 }
0876 
0877 #include "moc_flowpart.cpp"