/*************************************************************************** * Copyright (C) 2003-2005 by David Saxton * * david@bluehaze.org * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * ***************************************************************************/ #include "canvasitemparts.h" #include "connector.h" #include "flowcodedocument.h" #include "flowcode.h" #include "flowpart.h" #include "fpnode.h" #include "itemdocument.h" #include "itemdocumentdata.h" #include "microsettings.h" #include "micropackage.h" #include "picinfo.h" #include "pinmapping.h" #include "variant.h" #include #include #include #include #include #include #include #include #include // The following arrays of numbers represent the positions of nodes in different configurations, // with the numbers as NodeInfo::Position. Node::node_dir diamondNodePositioning[8][3] = { {Node::dir_up, Node::dir_down, Node::dir_right}, {Node::dir_up, Node::dir_down, Node::dir_left}, {Node::dir_up, Node::dir_right,Node::dir_down}, {Node::dir_up, Node::dir_right,Node::dir_left}, {Node::dir_left,Node::dir_right,Node::dir_down}, {Node::dir_left,Node::dir_right,Node::dir_up}, {Node::dir_left,Node::dir_down, Node::dir_right}, {Node::dir_left,Node::dir_down, Node::dir_up} }; Node::node_dir inOutNodePositioning[8][2] = { {Node::dir_up,Node::dir_down}, {Node::dir_up,Node::dir_right}, {Node::dir_up,Node::dir_left}, {Node::dir_right,Node::dir_right}, // (invalid) {Node::dir_left,Node::dir_right}, {Node::dir_left,Node::dir_down}, {Node::dir_left,Node::dir_up}, {Node::dir_right,Node::dir_right} }; // (invalid) Node::node_dir inNodePositioning[4] = {Node::dir_up,Node::dir_right,Node::dir_down,Node::dir_left}; Node::node_dir outNodePositioning[4] = {Node::dir_down,Node::dir_left,Node::dir_up,Node::dir_right}; FlowPart::FlowPart( ICNDocument *icnDocument, bool newItem, const TQString &id ) : CNItem( icnDocument, newItem, id ) { icnDocument->registerItem(this); m_pFlowCodeDocument = dynamic_cast(icnDocument); assert( m_pFlowCodeDocument ); m_flowSymbol = FlowPart::ps_other; m_orientation = 0; m_stdInput = 0l; m_stdOutput = 0l; m_altOutput = 0l; connect( m_pFlowCodeDocument, TQT_SIGNAL(picTypeChanged()), this, TQT_SLOT(slotUpdateFlowPartVariables()) ); connect( m_pFlowCodeDocument, TQT_SIGNAL(pinMappingsChanged()), this, TQT_SLOT(slotUpdateFlowPartVariables()) ); } FlowPart::~FlowPart() { // We have to check view, as if the item is deleted before the CNItem constructor // is called, then there will be no view if (m_pFlowCodeDocument) { const VariantDataMap::iterator end = m_variantData.end(); for ( VariantDataMap::iterator it = m_variantData.begin(); it != end; ++it ) { Variant *v = it.data(); if (v) m_pFlowCodeDocument->varNameChanged( "", v->value().toString() ); } } } void FlowPart::setCaption( const TQString &caption ) { if ( m_flowSymbol == FlowPart::ps_other ) { m_caption = caption; return; } TQWidget *w = new TQWidget(); TQPainter p(w); p.setFont( font() ); const int text_width = p.boundingRect( boundingRect(), (TQt::SingleLine | TQt::AlignHCenter | TQt::AlignVCenter), caption ).width(); p.end(); delete w; int width = std::max( ((int)(text_width/16))*16, 48 ); switch(m_flowSymbol) { case FlowPart::ps_call: { width += 48; break; } case FlowPart::ps_io: case FlowPart::ps_round: { width += 32; break; } case FlowPart::ps_decision: { width += 64; break; } case FlowPart::ps_process: default: { width += 32; break; } } bool hasSideConnectors = m_flowSymbol == FlowPart::ps_decision; if ( hasSideConnectors && (width != this->width()) ) p_icnDocument->requestRerouteInvalidatedConnectors(); initSymbol( m_flowSymbol, width ); m_caption = caption; } void FlowPart::postResize() { updateNodePositions(); CNItem::postResize(); } void FlowPart::createStdInput() { m_stdInput = (FPNode*)createNode( 0, 0, Node::dir_up, "stdinput", Node::fp_in ); updateNodePositions(); } void FlowPart::createStdOutput() { m_stdOutput = (FPNode*)createNode( 0, 0, Node::dir_down, "stdoutput", Node::fp_out ); updateNodePositions(); } void FlowPart::createAltOutput() { m_altOutput = (FPNode*)createNode( 0, 0, Node::dir_right, "altoutput", Node::fp_out ); updateNodePositions(); } void FlowPart::initSymbol( FlowPart::FlowSymbol symbol, int width ) { m_flowSymbol = symbol; switch(symbol) { case FlowPart::ps_other: { return; } case FlowPart::ps_call: case FlowPart::ps_process: { setItemPoints( TQRect( -width/2, -16, width, 24 ) ); break; } case FlowPart::ps_io: { // define parallelogram tqshape TQPointArray pa(4); pa[0] = TQPoint( -(width-10)/2, -16 ); pa[1] = TQPoint( width/2, -16 ); pa[2] = TQPoint( (width-10)/2, 8 ); pa[3] = TQPoint( -width/2, 8 ); setItemPoints(pa); break; } case FlowPart::ps_round: { // define rounded rectangles as two semicricles with RP_NUM/2 points with gap inbetween // These points are not used for drawing; merely for passing to qcanvaspolygonitem for collision detection // If there is a better way for a rounder rectangle + collision detection, please let me know... int halfHeight = 12; // Draw semicircle double x; const int RP_NUM = 48; TQPointArray pa(RP_NUM); int point = 0; for ( double y = -1.0; y <= 1.0; y+= 4.0/(RP_NUM-2) ) { x = sqrt(1-y*y)*halfHeight; pa[point] = TQPoint( (int)(width+x)-halfHeight, (int)(halfHeight*y) ); pa[RP_NUM-1-point] = TQPoint ( (int)(halfHeight-x), (int)(halfHeight*y) ); point++; } pa.translate( -width/2, 4 ); setItemPoints(pa); break; } case FlowPart::ps_decision: { // define rhombus TQPointArray pa(6); pa[0] = TQPoint( 0, -24 ); pa[1] = TQPoint( width/2, -6 ); pa[2] = TQPoint( width/2, 6 ); pa[3] = TQPoint( 0, 24 ); pa[4] = TQPoint( -width/2, 6 ); pa[5] = TQPoint( -width/2, -6 ); setItemPoints(pa); break; } default: kdError() << k_funcinfo << "Unknown flowSymbol: "<id(); } FlowPart* FlowPart::outputPart( const TQString& internalNodeId ) { Node *node = p_icnDocument->nodeWithID( nodeId(internalNodeId) ); FPNode *fpnode = dynamic_cast(node); if ( !fpnode || fpnode->type() == Node::fp_in ) return 0l; return fpnode->outputFlowPart(); } FlowPartList FlowPart::inputParts( const TQString& id ) { Node *node = p_icnDocument->nodeWithID(id); if ( FPNode *fpNode = dynamic_cast(node) ) return fpNode->inputFlowParts(); return FlowPartList(); } FlowPartList FlowPart::inputParts() { FlowPartList list; const NodeMap::iterator nEnd = m_nodeMap.end(); for ( NodeMap::iterator it = m_nodeMap.begin(); it != nEnd; ++it ) { Node *node = p_icnDocument->nodeWithID( it.data().id ); FlowPartList newList; if ( FPNode *fpNode = dynamic_cast(node) ) newList = fpNode->inputFlowParts(); const FlowPartList::iterator nlEnd = newList.end(); for ( FlowPartList::iterator it = newList.begin(); it != nlEnd; ++it ) { if (*it) list.append(*it); } } return list; } FlowPartList FlowPart::outputParts() { FlowPartList list; const NodeMap::iterator end = m_nodeMap.end(); for ( NodeMap::iterator it = m_nodeMap.begin(); it != end; ++it ) { FlowPart *part = outputPart( it.key() ); if (part) list.append(part); } return list; } FlowPart* FlowPart::endPart( TQStringList ids, FlowPartList *previousParts ) { if ( ids.empty() ) { const NodeMap::iterator end = m_nodeMap.end(); for ( NodeMap::iterator it = m_nodeMap.begin(); it != end; ++it ) { ids.append( it.key() ); } filterEndPartIDs( &ids ); } const bool createdList = (!previousParts); if (createdList) { previousParts = new FlowPartList; } else if ( previousParts->tqcontains(this) ) { return 0l; } previousParts->append(this); if ( ids.empty() ) { return 0l; } if ( ids.size() == 1 ) { return outputPart( *(ids.begin()) ); } typedef TQValueList ValidPartsList; ValidPartsList validPartsList; const TQStringList::iterator idsEnd = ids.end(); for ( TQStringList::iterator it = ids.begin(); it != idsEnd; ++it ) { int prevLevel = level(); FlowPartList validParts; FlowPart *part = outputPart(*it); while (part) { if ( !validParts.tqcontains(part) ) { validParts.append(part); // if ( part->level() >= level() ) { const int _l = part->level(); part = part->endPart( TQStringList(), previousParts ); prevLevel = _l; // } else { // part = 0l; // } } else { part = 0l; } } if ( !validParts.empty() ) { validPartsList.append(validParts); } } if (createdList) { delete previousParts; previousParts = 0l; } if ( validPartsList.empty() ) return 0l; FlowPartList firstList = *(validPartsList.begin()); const FlowPartList::iterator flEnd = firstList.end(); const ValidPartsList::iterator vplEnd = validPartsList.end(); for ( FlowPartList::iterator it = firstList.begin(); it != flEnd; ++it ) { bool ok = true; for ( ValidPartsList::iterator vplit = validPartsList.begin(); vplit != vplEnd; ++vplit ) { if ( !(*vplit).tqcontains(*it) ) ok = false; } if (ok) return *it; } return 0l; } void FlowPart::handleIfElse( FlowCode *code, const TQString &case1Statement, const TQString &case2Statement, const TQString &case1, const TQString &case2 ) { if (!code) return; FlowPart *stop = 0l; FlowPart *part1 = outputPart(case1); FlowPart *part2 = outputPart(case2); if ( part1 && part2 ) stop = endPart( TQStringList::split( ',', case1+","+case2 ) ); if ( (!part1 && !part2) || (part1 == stop && part2 == stop) ) return; code->addStopPart(stop); if ( part1 && part1 != stop && code->isValidBranch(part1) ) { // Use the case1 statement code->addCode( "if "+case1Statement+" then "+"\n{" ); code->addCodeBranch(part1); code->addCode("}"); if ( part2 && part2 != stop && code->isValidBranch(part2) ) { code->addCode( "else\n{" ); code->addCodeBranch(part2); code->addCode("}"); } } else if ( code->isValidBranch(part2) ) { // Use the case2 statement code->addCode( "if "+case2Statement+" then "+"\n{" ); code->addCodeBranch(part2); code->addCode("}"); } code->removeStopPart(stop); code->addCodeBranch(stop); } Variant * FlowPart::createProperty( const TQString & id, Variant::Type::Value type ) { if ( type != Variant::Type::Port && type != Variant::Type::Pin && type != Variant::Type::VarName && type != Variant::Type::SevenSegment && type != Variant::Type::KeyPad ) return CNItem::createProperty( id, type ); Variant * v = createProperty( id, Variant::Type::String ); v->setType(type); if ( type == Variant::Type::VarName ) { if ( MicroSettings * settings = m_pFlowCodeDocument->microSettings() ) v->setAllowed( settings->variableNames() ); connect( property(id), TQT_SIGNAL(valueChanged(TQVariant, TQVariant )), this, TQT_SLOT(varNameChanged(TQVariant, TQVariant )) ); } else slotUpdateFlowPartVariables(); return v; } void FlowPart::slotUpdateFlowPartVariables() { if (!m_pFlowCodeDocument) return; MicroSettings *s = m_pFlowCodeDocument->microSettings(); if (!s) return; const PinMappingMap pinMappings = s->pinMappings(); TQStringList sevenSegMaps; TQStringList keyPadMaps; PinMappingMap::const_iterator pEnd = pinMappings.end(); for ( PinMappingMap::const_iterator it = pinMappings.begin(); it != pEnd; ++it ) { switch ( it.data().type() ) { case PinMapping::SevenSegment: sevenSegMaps << it.key(); break; case PinMapping::Keypad_4x3: case PinMapping::Keypad_4x4: keyPadMaps << it.key(); break; case PinMapping::Invalid: break; } } TQStringList ports = s->microInfo()->package()->portNames(); ports.sort(); TQStringList pins = s->microInfo()->package()->pinIDs(PicPin::type_bidir | PicPin::type_input | PicPin::type_open); pins.sort(); const VariantDataMap::iterator vEnd = m_variantData.end(); for ( VariantDataMap::iterator it = m_variantData.begin(); it != vEnd; ++it ) { Variant * v = it.data(); if ( !v ) continue; if ( v->type() == Variant::Type::Port ) v->setAllowed( ports ); else if ( v->type() == Variant::Type::Pin ) v->setAllowed( pins ); else if ( v->type() == Variant::Type::SevenSegment ) { v->setAllowed( sevenSegMaps ); if ( !sevenSegMaps.isEmpty() && !sevenSegMaps.tqcontains( v->value().toString() ) ) v->setValue( sevenSegMaps.first() ); } else if ( v->type() == Variant::Type::KeyPad ) { v->setAllowed( keyPadMaps ); if ( !keyPadMaps.isEmpty() && !keyPadMaps.tqcontains( v->value().toString() ) ) v->setValue( keyPadMaps.first() ); } } } void FlowPart::updateVarNames() { if (!m_pFlowCodeDocument) return; MicroSettings *s = m_pFlowCodeDocument->microSettings(); if (!s) return; const TQStringList names = s->variableNames(); const VariantDataMap::iterator end = m_variantData.end(); for ( VariantDataMap::iterator it = m_variantData.begin(); it != end; ++it ) { Variant *v = it.data(); if ( v && v->type() == Variant::Type::VarName ) v->setAllowed(names); } } void FlowPart::varNameChanged( TQVariant newValue, TQVariant oldValue ) { if (!m_pFlowCodeDocument) return; m_pFlowCodeDocument->varNameChanged( newValue.asString(), oldValue.asString() ); } inline int nodeDirToPos( Node::node_dir dir ) { switch (dir) { case Node::dir_right: return 0; case Node::dir_up: return 1; case Node::dir_left: return 2; case Node::dir_down: return 3; } return 0; } void FlowPart::updateAttachedPositioning( ) { if (b_deleted) return; //BEGIN Rearrange text if appropriate const TQRect textPos[4] = { TQRect( offsetX()+width(), 6, 40, 16 ), TQRect( 0, offsetY()-16, 40, 16 ), TQRect( offsetX()-40, 6, 40, 16 ), TQRect( 0, offsetY()+height(), 40, 16 ) }; NodeInfo * stdOutputInfo = m_stdOutput ? &m_nodeMap["stdoutput"] : 0; NodeInfo * altOutputInfo = m_altOutput ? &m_nodeMap["altoutput"] : 0l; Text *outputTrueText = m_textMap.tqcontains("output_true") ? m_textMap["output_true"] : 0l; Text *outputFalseText = m_textMap.tqcontains("output_false") ? m_textMap["output_false"] : 0l; if ( stdOutputInfo && outputTrueText ) outputTrueText->setOriginalRect( textPos[ nodeDirToPos( (Node::node_dir)stdOutputInfo->orientation ) ] ); if ( altOutputInfo && outputFalseText ) outputFalseText->setOriginalRect( textPos[ nodeDirToPos( (Node::node_dir)altOutputInfo->orientation ) ] ); const TextMap::iterator textMapEnd = m_textMap.end(); for ( TextMap::iterator it = m_textMap.begin(); it != textMapEnd; ++it ) { TQRect pos = it.data()->recommendedRect(); it.data()->move( pos.x() + x(), pos.y() + y() ); it.data()->setGuiPartSize( pos.width(), pos.height() ); } //END Rearrange text if appropriate const NodeMap::iterator end = m_nodeMap.end(); for ( NodeMap::iterator it = m_nodeMap.begin(); it != end; ++it ) { if ( !it.data().node ) { kdError() << k_funcinfo << "Node in nodemap is null" << endl; continue; } double nx = it.data().x; double ny = it.data().y; #define round_8(x) (((x) > 0) ? int(((x)+4)/8)*8 : int(((x)-4)/8)*8) nx = round_8(nx); ny = round_8(ny); #undef round_8 it.data().node->move( int(nx+x()), int(ny+y()) ); it.data().node->setOrientation( (Node::node_dir)it.data().orientation ); } } ItemData FlowPart::itemData( ) const { ItemData itemData = CNItem::itemData(); itemData.orientation = m_orientation; return itemData; } void FlowPart::restoreFromItemData( const ItemData & itemData ) { CNItem::restoreFromItemData(itemData); if ( itemData.orientation >= 0 ) setOrientation( uint(itemData.orientation) ); } void FlowPart::updateNodePositions() { if ( m_orientation > 7 ) { kdWarning() << k_funcinfo << "Invalid orientation: "<orientation = diamondNodePositioning[m_orientation][0]; stdOutputInfo->orientation = diamondNodePositioning[m_orientation][1]; altOutputInfo->orientation = diamondNodePositioning[m_orientation][2]; } else if ( m_stdInput && m_stdOutput ) { stdInputInfo->orientation = inOutNodePositioning[m_orientation][0]; stdOutputInfo->orientation = inOutNodePositioning[m_orientation][1]; } else if ( m_orientation < 4 ) { if (stdInputInfo) stdInputInfo->orientation = inNodePositioning[m_orientation]; else if (stdOutputInfo) stdOutputInfo->orientation = outNodePositioning[m_orientation]; } else { kdWarning() << k_funcinfo << "Invalid orientation: "<requestRerouteInvalidatedConnectors(); } uint FlowPart::allowedOrientations( ) const { // The bit positions shown here represent whether or not that orientation is allowed, the orientation being // what is displayed in the i'th position (0 to 3 on top, 4 to 7 on bottom) of orientation widget if ( m_stdInput && m_stdOutput && m_altOutput ) return 255; if ( m_stdInput && m_stdOutput ) return 119; if ( m_stdInput || m_stdOutput ) return 15; return 0; } void FlowPart::orientationPixmap( uint orientation, TQPixmap & pm ) const { const TQSize size = pm.size(); if ( ! ( allowedOrientations() & ( 1 << orientation ) ) ) { kdWarning() << k_funcinfo << "Requesting invalid orientation of " << orientation << endl; return; } TQBitmap tqmask( 50, 50 ); TQPainter maskPainter(&tqmask); tqmask.fill( TQt::color0 ); maskPainter.setBrush(TQt::color1); maskPainter.setPen(TQt::color1); TQPainter p(&pm); p.setBrush(m_brushCol); p.setPen( TQt::black ); // In order: right corner, top corner, left corner, bottom corner TQPoint c[4] = { TQPoint( int(0.7*size.width()), int(0.5*size.height()) ), TQPoint( int(0.5*size.width()), int(0.4*size.height()) ), TQPoint( int(0.3*size.width()), int(0.5*size.height()) ), TQPoint( int(0.5*size.width()), int(0.6*size.height()) ) }; TQPoint d[4]; d[0] = c[0] + TQPoint( 7, 0 ); d[1] = c[1] + TQPoint( 0, -7 ); d[2] = c[2] + TQPoint( -7, 0 ); d[3] = c[3] + TQPoint( 0, 7 ); if ( m_stdInput && m_stdOutput && m_altOutput ) { //BEGIN Draw diamond outline TQPointArray diamond(4); for ( uint i=0; i<4; ++i ) diamond[i] = c[i]; p.drawPolygon(diamond); maskPainter.drawPolygon(diamond); //END Draw diamond outline //BEGIN Draw input int pos0 = nodeDirToPos( diamondNodePositioning[orientation][0] ); p.drawLine( c[pos0], d[pos0] ); maskPainter.drawLine( c[pos0], d[pos0] ); //END Draw input //BEGIN Draw "true" output as a tick TQPointArray tick(4); tick[0] = TQPoint( -3, 0 ); tick[1] = TQPoint( 0, 2 ); tick[2] = TQPoint( 0, 2 ); tick[3] = TQPoint( 4, -2 ); int pos1 = nodeDirToPos( diamondNodePositioning[orientation][1] ); tick.translate( d[pos1].x(), d[pos1].y() ); p.drawLineSegments(tick); maskPainter.drawLineSegments(tick); //END Draw "true" output as a tick //BEGIN Draw "false" output as a cross TQPointArray cross(4); cross[0] = TQPoint( -2, -2 ); cross[1] = TQPoint( 2, 2 ); cross[2] = TQPoint( -2, 2 ); cross[3] = TQPoint( 2, -2 ); int pos2 = nodeDirToPos( diamondNodePositioning[orientation][2] ); cross.translate( d[pos2].x(), d[pos2].y() ); p.drawLineSegments(cross); maskPainter.drawLineSegments(cross); //END Draw "false" output as a cross } else if ( m_stdInput || m_stdOutput ) { p.drawRoundRect( int(0.3*size.width()), int(0.4*size.height()), int(0.4*size.width()), int(0.2*size.height()) ); maskPainter.drawRoundRect( int(0.3*size.width()), int(0.4*size.height()), int(0.4*size.width()), int(0.2*size.height()) ); int hal = 5; // half arrow length int haw = 3; // half arrow width TQPoint arrows[4][6] = { { TQPoint( hal, 0 ), TQPoint( 0, -haw ), TQPoint( hal, 0 ), TQPoint( -hal, 0 ), TQPoint( hal, 0 ), TQPoint( 0, haw ) }, { TQPoint( 0, -hal ), TQPoint( -haw, 0 ), TQPoint( 0, -hal ), TQPoint( 0, hal ), TQPoint( 0, -hal ), TQPoint( haw, 0 ) }, { TQPoint( -hal, 0 ), TQPoint( 0, -haw ), TQPoint( -hal, 0 ), TQPoint( hal, 0 ), TQPoint( -hal, 0 ), TQPoint( 0, haw ) }, { TQPoint( 0, hal ), TQPoint( -haw, 0 ), TQPoint( 0, hal ), TQPoint( 0, -hal ), TQPoint( 0, hal ), TQPoint( haw, 0 ) } }; int inPos = -1; int outPos = -1; if ( m_stdInput && m_stdOutput ) { inPos = nodeDirToPos( inOutNodePositioning[orientation][0] ); outPos = nodeDirToPos( inOutNodePositioning[orientation][1] ); } else if ( m_stdInput ) { inPos = nodeDirToPos( inNodePositioning[orientation] ); } else if ( m_stdOutput ) { outPos = nodeDirToPos( outNodePositioning[orientation] ); } if ( inPos != -1 ) { TQPointArray inArrow(6); for ( int i=0; i<6; ++i ) { inArrow[i] = arrows[(inPos+2)%4][i]; } inArrow.translate( d[inPos].x(), d[inPos].y() ); p.drawPolygon(inArrow); maskPainter.drawPolygon(inArrow); } if ( outPos != -1 ) { TQPointArray outArrow(6); for ( int i=0; i<6; ++i ) { outArrow[i] = arrows[outPos][i]; } outArrow.translate( d[outPos].x(), d[outPos].y() ); p.drawPolygon(outArrow); maskPainter.drawPolygon(outArrow); } } pm.setMask(tqmask); } #include "flowpart.moc"