/* * Kivio - Visual Modelling and Flowcharting * Copyright (C) 2000-2001 theKompany.com & Dave Marotti * * 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. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ /* * Implementing a connector involves deriving from this class. * * You need to implement the following routines: * setStartPoint * setEndPoint * checkForCollision * duplicate * paint * paintOutline * saveXML * loadXML * * Save and Load should call saveConnectors, loadConnectors, saveProperties, and * loadProperties. These are helper functions which take care of the common * acts of saving/loading connectors and colors/line-styles, etc... * */ #include "kivio_1d_stencil.h" #include "kivio_arrowhead.h" #include "kivio_common.h" #include "kivio_connector_point.h" #include "kivio_custom_drag_data.h" #include "kivio_fill_style.h" #include "kivio_intra_stencil_data.h" #include "kivio_layer.h" #include "kivio_line_style.h" #include "kivio_page.h" #include "kivio_painter.h" #include "kivio_point.h" #include "kivio_stencil.h" #include "kivio_stencil_spawner.h" #include "kivio_stencil_spawner_info.h" #include "kivio_stencil_spawner_set.h" #include "kivio_text_style.h" #include "kivio_connector_target.h" #include #include #include #include #include /** * Default constructor. * * This will allocate a new fill style object, a list for * connection points, and set some default values. */ Kivio1DStencil::Kivio1DStencil() : KivioStencil() { m_pFillStyle = new KivioFillStyle(); m_pLineStyle = new KivioLineStyle(); m_pTextStyle = new KivioTextStyle(); m_pConnectorPoints = new TQPtrList; m_pConnectorPoints->setAutoDelete(true); m_pStart = new KivioConnectorPoint(this, true); m_pStart->setPosition(72.0f, 18.0f, false); m_pEnd = new KivioConnectorPoint(this, true); m_pEnd->setPosition(0.0f, 18.0f, false); m_pLeft = new KivioConnectorPoint(this, false); m_pLeft->setPosition(36.0f, 36.0f, false); m_pRight = new KivioConnectorPoint(this, false); m_pRight->setPosition(36.0f, 0.0f, false); m_pTextConn = new KivioConnectorPoint(this, false); m_pTextConn->setPosition(36.0f, 18.0f, false); m_connectorWidth = 36.0f; m_needsWidth = true; m_needsText = false; m_pConnectorPoints->append( m_pStart ); m_pConnectorPoints->append( m_pEnd ); m_pConnectorPoints->append( m_pLeft ); m_pConnectorPoints->append( m_pRight ); m_pConnectorPoints->append( m_pTextConn ); } /** * Destructor */ Kivio1DStencil::~Kivio1DStencil() { delete m_pFillStyle; delete m_pLineStyle; delete m_pTextStyle; delete m_pConnectorPoints; } void Kivio1DStencil::setFGColor( TQColor c ) { m_pLineStyle->setColor(c); } TQColor Kivio1DStencil::fgColor() { return m_pLineStyle->color(); } void Kivio1DStencil::setLineWidth( double f ) { m_pLineStyle->setWidth(f); } double Kivio1DStencil::lineWidth() { return m_pLineStyle->width(); } void Kivio1DStencil::setLinePattern(int p) { m_pLineStyle->setStyle(p); } int Kivio1DStencil::linePattern() { return m_pLineStyle->style(); } void Kivio1DStencil::setFillPattern(int p) { m_pFillStyle->setBrushStyle(static_cast(p)); } int Kivio1DStencil::fillPattern() { return m_pFillStyle->brushStyle(); } void Kivio1DStencil::setBGColor( TQColor c ) { m_pFillStyle->setColor(c); } TQColor Kivio1DStencil::bgColor() { return m_pFillStyle->color(); } ///////////////////////////////// // Position functions ///////////////////////////////// void Kivio1DStencil::setX( double x ) { double dx = x - m_x; KivioConnectorPoint *p = m_pConnectorPoints->first(); while( p ) { p->disconnect(); p->setX( p->x() + dx, false ); p = m_pConnectorPoints->next(); } m_x = x; } void Kivio1DStencil::setY( double y ) { double dy = y - m_y; KivioConnectorPoint *p = m_pConnectorPoints->first(); while( p ) { p->disconnect(); p->setY( p->y() + dy, false ); p = m_pConnectorPoints->next(); } m_y = y; } void Kivio1DStencil::setPosition( double x, double y ) { double dx = x - m_x; double dy = y - m_y; m_x += dx; m_y += dy; KivioConnectorPoint *p = m_pConnectorPoints->first(); while( p ) { p->setPosition( p->x()+dx, p->y()+dy, false ); p->disconnect(); p = m_pConnectorPoints->next(); } m_x = x; m_y = y; } ///////////////////////////// // Connection tool functions ///////////////////////////// void Kivio1DStencil::setStartPoint( double x, double y ) { double oldX = m_pStart->x(); double oldY = m_pStart->y(); m_pStart->setPosition(x, y, false); m_pStart->disconnect(); updateConnectorPoints(m_pStart, oldX, oldY); } void Kivio1DStencil::setEndPoint( double x, double y ) { double oldX = m_pEnd->x(); double oldY = m_pEnd->y(); m_pEnd->setPosition(x, y, false); m_pEnd->disconnect(); updateConnectorPoints(m_pEnd, oldX, oldY); } void Kivio1DStencil::updateConnectorPoints( KivioConnectorPoint *p, double /*oldX*/, double /*oldY*/ ) { // If p is the start or end, we need to adjust the width connectors if( p == m_pStart || p == m_pEnd ) { double vx = m_pStart->x() - m_pEnd->x(); double vy = m_pStart->y() - m_pEnd->y(); double len = sqrt( vx*vx + vy*vy ); double midX = (m_pStart->x() + m_pEnd->x())/2.0f; double midY = (m_pStart->y() + m_pEnd->y())/2.0f; vx /= len; vy /= len; double d = m_connectorWidth/2.0f; m_pLeft->setPosition( midX + d*vy, midY + d*(-vx), false ); m_pRight->setPosition( midX + d*(-vy), midY + d*vx, false ); } updateGeometry(); } void Kivio1DStencil::paint( KivioIntraStencilData * ) { /* Derived class must implement this */ } void Kivio1DStencil::paintOutline( KivioIntraStencilData *pData ) { /* Derived class should implement this */ paint( pData ); } void Kivio1DStencil::paintConnectorTargets( KivioIntraStencilData * ) { } void Kivio1DStencil::paintSelectionHandles( KivioIntraStencilData *pData ) { KivioPainter *painter = pData->painter; double x1, y1; int flag; KoZoomHandler* zoomHandler = pData->zoomHandler; KivioConnectorPoint *p = m_pConnectorPoints->first(); while( p ) { // If we don't need width connectors and we are on a width connector, // ignore it. x1 = zoomHandler->zoomItX(p->x()); y1 = zoomHandler->zoomItY(p->y()); flag = (p->target()) ? KivioPainter::cpfConnected : 0; if( p==m_pTextConn ) { if( m_needsText==true ) { painter->drawHandle( x1, y1, 0 ); } } else if( p==m_pLeft || p==m_pRight ) { if( m_needsWidth==true ) { painter->drawHandle( x1, y1, 0 ); } } else if( p==m_pStart ) { painter->drawHandle( x1, y1, KivioPainter::cpfStart | flag ); } else if( p==m_pEnd ) { painter->drawHandle( x1, y1, KivioPainter::cpfEnd | flag ); } else { if( p->connectable() ) { painter->drawHandle( x1, y1, KivioPainter::cpfConnectable | flag ); } else { painter->drawHandle( x1, y1, flag ); } } p = m_pConnectorPoints->next(); } } /////////////////////////////// // Collision detection /////////////////////////////// KivioCollisionType Kivio1DStencil::checkForCollision( KoPoint *, double ) { /* Derived class must implement this */ return kctNone; } ///////////////////////////////// // Custom dragging ///////////////////////////////// /** * Custom drag the connector points. * * The default action of this function is to locate the point * in the connector list by the id and then drag it around. * Then attempt to snap it to another stencil. Otherwise * disconnect it. */ void Kivio1DStencil::customDrag( KivioCustomDragData *pData ) { setCustomIDPoint(pData->id, KoPoint(pData->x, pData->y), pData->page); } /** * Sets the position and dimensions of this stencil based on its connection points. */ void Kivio1DStencil::updateGeometry() { double minX, minY, maxX, maxY; minX = 1000000000000.0f; minY = minX; maxX = -100000000000.0f; maxY = maxX; KivioConnectorPoint *p; p = m_pConnectorPoints->first(); while( p ) { if( p->x() < minX ) minX = p->x(); if( p->x() > maxX ) maxX = p->x(); if( p->y() < minY ) minY = p->y(); if( p->y() > maxY ) maxY = p->y(); p = m_pConnectorPoints->next(); } m_x = minX; m_y = minY; m_w = maxX - minX + 1.0f; m_h = maxY - minY + 1.0f; } // file i/o routines bool Kivio1DStencil::loadXML( const TQDomElement &e ) { TQDomNode node; TQString name; node = e.firstChild(); while( !node.isNull() ) { name = node.nodeName(); if( name == "KivioStencilProperties" ) { loadProperties(node.toElement() ); } node = node.nextSibling(); } updateGeometry(); return true; } TQDomElement Kivio1DStencil::createRootElement( TQDomDocument &doc ) { TQDomElement e = doc.createElement("KivioPluginStencil"); XmlWriteString( e, "id", m_pSpawner->info()->id() ); XmlWriteString( e, "setId", m_pSpawner->set()->id() ); return e; } TQDomElement Kivio1DStencil::saveXML( TQDomDocument &doc ) { TQDomElement e = createRootElement(doc); e.appendChild( saveProperties(doc) ); return e; } KivioStencil *Kivio1DStencil::duplicate() { /* Derived class must implement this function */ return NULL; } bool Kivio1DStencil::boolAllTrue( bool *boolArray, int count ) { int i; for( i=0; icount()]; unsigned int i; for(i = 0; i < m_pConnectorPoints->count(); i++) { done[i] = false; } KivioConnectorPoint *p; i = 0; p = m_pConnectorPoints->first(); while( p ) { if( p->targetId() == -1 ) { done[i] = true; } i++; p = m_pConnectorPoints->next(); } // No connections? BaiL! if( boolAllTrue( done, m_pConnectorPoints->count() ) ) { delete [] done; return; } KivioLayer *pLayer = pPage->firstLayer(); while( pLayer && ( boolContainsFalse(done, m_pConnectorPoints->count()) ) ) { KivioStencil *pStencil = pLayer->firstStencil(); while( pStencil && ( boolContainsFalse(done, m_pConnectorPoints->count()) ) ) { // No connecting to ourself! if((pStencil != this)) { // Iterate through all connectors attempting to connect it to the stencil. // If it connects, mark it as done i=0; p = m_pConnectorPoints->first(); while( p ) { if( !done[i] && p->targetId() != -1 ) { if(pStencil->connectToTarget( p, p->targetId())) { done[i] = true; } } i++; p = m_pConnectorPoints->next(); } } pStencil = pLayer->nextStencil(); } pLayer = pPage->nextLayer(); } delete [] done; } void Kivio1DStencil::searchForConnections( KivioPage *pPage, double threshold ) { bool *done = new bool[ m_pConnectorPoints->count()]; int i; for( i=0; i<(int)m_pConnectorPoints->count(); i++ ) { done[i] = false; } KivioConnectorPoint *p; i = 0; p = m_pConnectorPoints->first(); while( p ) { if(p->target() != 0L) { done[i] = true; } i++; p = m_pConnectorPoints->next(); } // No connections? BaiL! if( boolAllTrue( done, m_pConnectorPoints->count() ) ) { delete [] done; return; } KivioLayer *pLayer = pPage->firstLayer(); while( pLayer && ( boolContainsFalse(done, m_pConnectorPoints->count()) ) ) { KivioStencil *pStencil = pLayer->firstStencil(); while( pStencil && ( boolContainsFalse(done, m_pConnectorPoints->count()) ) ) { // No connecting to ourself! if( pStencil != this ) { // Iterate through all connectors attempting to connect it to the stencil. // If it connects, mark it as done i = 0; p = m_pConnectorPoints->first(); while( p ) { if( !done[i] && p->target() == 0 ) { if( pStencil->connectToTarget( p, threshold ) ) { done[i] = true; } } i++; p = m_pConnectorPoints->next(); } } pStencil = pLayer->nextStencil(); } pLayer = pPage->nextLayer(); } delete [] done; } ////////////////////// // resize handles ////////////////////// int Kivio1DStencil::resizeHandlePositions() { return (int)krhpNone; } TQDomElement Kivio1DStencil::saveConnectors( TQDomDocument &doc ) { TQDomElement eConns = doc.createElement("KivioConnectorList"); KivioConnectorPoint *p; p = m_pConnectorPoints->first(); while( p ) { eConns.appendChild( p->saveXML(doc) ); p = m_pConnectorPoints->next(); } return eConns; } bool Kivio1DStencil::loadConnectors( const TQDomElement &e ) { m_pConnectorPoints->clear(); KivioConnectorPoint *p; TQDomNode node = e.firstChild(); TQDomElement e2; TQString name; while( !node.isNull() ) { e2 = node.toElement(); name = e2.nodeName(); if( name == "KivioConnectorPoint" ) { p = new KivioConnectorPoint(); p->setStencil(this); p->loadXML( e2 ); m_pConnectorPoints->append( p ); p = NULL; } node = node.nextSibling(); } // Set the pointers to the start,end,left,right points m_pStart = m_pConnectorPoints->first(); m_pEnd = m_pConnectorPoints->next(); m_pLeft = m_pConnectorPoints->next(); m_pRight = m_pConnectorPoints->next(); m_pTextConn = m_pConnectorPoints->next(); // Hopefully this will help with backwards compatibility if( m_pStart == NULL ) { m_pStart = new KivioConnectorPoint(this, true); } if( m_pEnd == NULL ) { m_pEnd = new KivioConnectorPoint(this, true); } if( m_pLeft == NULL ) { m_pLeft = new KivioConnectorPoint(this, false); } if( m_pRight == NULL ) { m_pRight = new KivioConnectorPoint(this, false); } if( m_pTextConn == NULL ) { m_pTextConn = new KivioConnectorPoint(this, false); } return true; } TQDomElement Kivio1DStencil::saveProperties( TQDomDocument &doc ) { TQDomElement propE = doc.createElement("KivioStencilProperties"); TQDomElement connE = doc.createElement("Kivio1DProperties"); XmlWriteFloat( connE, "connectorWidth", m_connectorWidth ); XmlWriteInt( connE, "needsWidth", m_needsWidth ); propE.appendChild( connE ); propE.appendChild( m_pLineStyle->saveXML( doc ) ); propE.appendChild( m_pFillStyle->saveXML( doc ) ); propE.appendChild( m_pTextStyle->saveXML( doc ) ); propE.appendChild( saveConnectors(doc) ); TQDomElement customE = doc.createElement("CustomData"); if( saveCustom( customE, doc )==true ) { propE.appendChild( customE ); } return propE; } bool Kivio1DStencil::loadProperties( const TQDomElement &e ) { TQDomNode node; TQDomElement nodeE; TQString nodeName; node = e.firstChild(); while( !node.isNull() ) { nodeE = node.toElement(); nodeName = node.nodeName(); if( nodeName == "KivioFillStyle" ) { m_pFillStyle->loadXML( nodeE ); } else if( nodeName == "KivioLineStyle" ) { m_pLineStyle->loadXML( nodeE ); } else if( nodeName == "KivioTextStyle" ) { m_pTextStyle->loadXML( nodeE ); } else if( nodeName == "KivioConnectorList" ) { loadConnectors( nodeE ); } else if( nodeName == "Kivio1DProperties" ) { m_needsWidth = (bool)XmlReadInt( nodeE, "needsWidth", (int)true ); m_connectorWidth = XmlReadFloat( nodeE, "connectorWidth", 36.0f ); } else if( nodeName == "CustomData" ) { loadCustom( nodeE ); } node = node.nextSibling(); } return true; } bool Kivio1DStencil::loadCustom( const TQDomElement & ) { return true; } bool Kivio1DStencil::saveCustom( TQDomElement &, TQDomDocument & ) { return false; } void Kivio1DStencil::copyBasicInto( Kivio1DStencil *pStencil ) { KivioConnectorPoint *pSrc, *pTg; // Copy the spawner pStencil->setSpawner( m_pSpawner ); // Copy the connector points pSrc = m_pConnectorPoints->first(); pTg = pStencil->m_pConnectorPoints->first(); while( pSrc && pTg ) { pTg->setPosition( pSrc->x(), pSrc->y(), false ); pSrc = m_pConnectorPoints->next(); pTg = pStencil->m_pConnectorPoints->next(); } // Copy the dimensions pStencil->m_x = m_x; pStencil->m_y = m_y; pStencil->m_w = m_w; pStencil->m_h = m_h; // Copy over the width info pStencil->m_connectorWidth = m_connectorWidth; pStencil->m_needsWidth = m_needsWidth; // Copy over the styles m_pFillStyle->copyInto( pStencil->m_pFillStyle ); m_pLineStyle->copyInto( pStencil->m_pLineStyle ); m_pTextStyle->copyInto( pStencil->m_pTextStyle ); // Copy over the protection *(pStencil->m_pProtection) = *m_pProtection; *(pStencil->m_pCanProtect) = *m_pCanProtect; } void Kivio1DStencil::drawText( KivioIntraStencilData *pData ) { if(m_pTextStyle->text().isEmpty()) { return; } KoZoomHandler* zoomHandler = pData->zoomHandler; KivioPainter *painter = pData->painter; int _x, _y, _w, _h; _x = zoomHandler->zoomItX(m_pTextConn->x()); _y = zoomHandler->zoomItY(m_pTextConn->y()); _w = 10000000; _h = 10000000; TQFont f = m_pTextStyle->font(); int tf = m_pTextStyle->hTextAlign() | m_pTextStyle->vTextAlign(); f.setPointSizeFloat(f.pointSizeFloat() * (((float)zoomHandler->zoom()) / 100.0)); painter->setFont(f); TQRect boundRect = painter->boundingRect( _x, _y, _w, _h, tf, m_pTextStyle->text() ); TQPixmap pix(boundRect.width(), boundRect.height()); pix.fill(); TQPainter p(&pix); p.setPen(m_pTextStyle->color()); p.setFont(f); p.drawText( 0, 0, boundRect.width(), boundRect.height(), tf, m_pTextStyle->text() ); TQBitmap mask; mask = pix; pix.setMask(mask); painter->drawPixmap(_x, _y, pix); } bool Kivio1DStencil::connected() { KivioConnectorPoint *p; p = m_pConnectorPoints->first(); while( p ) { if(p->target() != 0) { return true; } p = m_pConnectorPoints->next(); } return false; } void Kivio1DStencil::disconnectFromTargets() { KivioConnectorPoint *p; p = m_pConnectorPoints->first(); while( p ) { p->disconnect(true); p = m_pConnectorPoints->next(); } } KivioLineStyle Kivio1DStencil::lineStyle() { return *m_pLineStyle; } void Kivio1DStencil::setLineStyle(KivioLineStyle ls) { ls.copyInto(m_pLineStyle); } void Kivio1DStencil::setCustomIDPoint(int customID, const KoPoint& point, KivioPage* page) { double oldX, oldY; KivioConnectorPoint *p; // Locate the point specified by customID p = m_pConnectorPoints->at( customID - (kctCustom+1)); if( !p ) { kdDebug(43000) << "Kivio1DStencil::customDrag() - KivioConnectorPoint customID: " << (customID - (kctCustom+1)) << " not found\n" << endl; return; } oldX = p->x(); oldY = p->y(); p->setPosition(point.x(),point.y(),true); if( p->connectable()==true ) { // Attempt a snap.... KivioLayer *pCurLayer = page->curLayer(); KivioLayer *pLayer = page->firstLayer(); //page->curLayer(); bool foundConnection = false; while( pLayer && !foundConnection ) { // To be connected to, a layer must be visible and connectable if( pLayer!=pCurLayer ) { if( pLayer->connectable()==false || pLayer->visible()==false ) { pLayer = page->nextLayer(); continue; } } // Tell the layer to search for a target if( pLayer->connectPointToTarget( p, 8.0f ) ) { foundConnection = true; } pLayer = page->nextLayer(); } if( foundConnection == false ) { p->disconnect(); } } // If it is a start/end point, then make a request to update the connectors (must be implemented by stencil developer) if( customID == kctCustom+1 || customID == kctCustom+2 ) { // If it's the end connector, then update the text point if( p==m_pEnd && m_needsText==true ) { m_pTextConn->setPosition( m_pTextConn->x() + (m_pEnd->x() - oldX), m_pTextConn->y() + (m_pEnd->y() - oldY), false ); } updateConnectorPoints(p, oldX, oldY); } // If it is one of the width handles, then fix the width and update the opposite point // only if the stencils 'needs width' connectors else if( (customID == kctCustom+3 || customID == kctCustom+4) && (m_needsWidth==true) ) { double vx = m_pStart->x() - m_pEnd->x(); double vy = m_pStart->y() - m_pEnd->y(); double len = sqrt( vx*vx + vy*vy ); double midX = (m_pStart->x() + m_pEnd->x())/2.0f; double midY = (m_pStart->y() + m_pEnd->y())/2.0f; vx /= len; vy /= len; double d = shortestDistance( m_pStart, m_pEnd, (customID==kctCustom+3) ? m_pLeft : m_pRight ); m_pLeft->setPosition( midX + d*vy, midY + d*(-vx), false ); m_pRight->setPosition( midX + d*(-vy), midY + d*vx, false ); m_connectorWidth = d*2.0f; updateConnectorPoints(p, oldX, oldY); return; } // Text handle else if( customID == kctCustom+5 ) { updateConnectorPoints(p, oldX, oldY); } } KoPoint Kivio1DStencil::customIDPoint(int customID) { KivioConnectorPoint *p; // Locate the point specified by customID p = m_pConnectorPoints->at( customID - (kctCustom+1)); return p->position(); }