/*************************************************************** * Name: DiagramManager.cpp * Purpose: Implements shape manager class * Author: Michal Bližňák (michal.bliznak@tiscali.cz) * Created: 2007-07-25 * Copyright: Michal Bližňák * License: wxWidgets license (www.wxwidgets.org) * Notes: **************************************************************/ #include "wx_pch.h" #ifdef _DEBUG_MSVC #define new DEBUG_NEW #endif #include #include #include #include "wx/wxsf/DiagramManager.h" #include "wx/wxsf/ShapeCanvas.h" #include "wx/wxsf/ControlShape.h" #include "wx/wxsf/LineShape.h" #include "wx/wxsf/GridShape.h" using namespace wxSFCommonFcn; // TODO: implement autolayout // TODO: implement better line X ellipse check WX_DEFINE_LIST(IDList); XS_IMPLEMENT_CLONABLE_CLASS(wxSFDiagramManager, wxXmlSerializer); wxSFDiagramManager::wxSFDiagramManager() { m_pShapeCanvas = NULL; m_lstIDPairs.DeleteContents(true); m_sSFVersion = wxT("1.12.4 beta"); SetSerializerOwner(wxT("wxShapeFramework")); SetSerializerVersion(wxT("1.0")); SetSerializerRootName(wxT("chart")); } wxSFDiagramManager::wxSFDiagramManager(const wxSFDiagramManager &obj) : wxXmlSerializer(obj) { m_pShapeCanvas = NULL; m_sSFVersion = obj.m_sSFVersion; m_lstIDPairs.DeleteContents(true); } wxSFDiagramManager::~wxSFDiagramManager() { Clear(); } //----------------------------------------------------------------------------------// // Adding/removing shapes functions //----------------------------------------------------------------------------------// wxSFShapeBase* wxSFDiagramManager::AddShape(wxClassInfo* shapeInfo, bool saveState, wxSF::ERRCODE *err) { wxPoint shapePos; if(m_pShapeCanvas) { wxRect crect = m_pShapeCanvas->GetClientRect(); shapePos = wxPoint((crect.GetRight() - crect.GetLeft())/2, (crect.GetBottom() - crect.GetTop())/2); } wxSFShapeBase* pShape = AddShape(shapeInfo, shapePos, saveState, err); return pShape; } wxSFShapeBase* wxSFDiagramManager::AddShape(wxClassInfo* shapeInfo, const wxPoint& pos, bool saveState, wxSF::ERRCODE *err) { wxASSERT( shapeInfo ); if( shapeInfo && IsShapeAccepted(shapeInfo->GetClassName()) ) { // create shape object from class info wxSFShapeBase *pShape = (wxSFShapeBase*)shapeInfo->CreateObject(); wxSFShapeBase *pParentShape = NULL; // update given possition wxPoint lpos = pos; if( m_pShapeCanvas ) { lpos = m_pShapeCanvas->FitPositionToGrid( m_pShapeCanvas->DP2LP(pos) ); } // line shapes can be assigned to root only if( !pShape->IsKindOf(CLASSINFO(wxSFLineShape)) ) pParentShape = GetShapeAtPosition(lpos); if( pParentShape && pParentShape->IsChildAccepted(shapeInfo->GetClassName()) ) { pShape = AddShape(pShape, (xsSerializable*)pParentShape, pos - Conv2Point( pParentShape->GetAbsolutePosition() ), sfINITIALIZE, saveState, err); } else pShape = AddShape(pShape, GetRootItem(), pos, sfINITIALIZE, saveState, err); if( pParentShape )pParentShape->Update(); return pShape; } else { if( err ) *err = wxSF::errNOT_ACCEPTED; return NULL; } } wxSFShapeBase* wxSFDiagramManager::AddShape(wxSFShapeBase* shape, xsSerializable* parent, const wxPoint& pos, bool initialize, bool saveState, wxSF::ERRCODE *err) { if(shape) { if( shape->IsKindOf(CLASSINFO(wxSFShapeBase)) && IsShapeAccepted(shape->GetClassInfo()->GetClassName()) ) { if( m_pShapeCanvas ) { wxPoint newPos = m_pShapeCanvas->FitPositionToGrid(m_pShapeCanvas->DP2LP(pos)); shape->SetRelativePosition( Conv2RealPoint(newPos) ); } else shape->SetRelativePosition( Conv2RealPoint(pos) ); // add parent shape to the data manager (serializer) if(parent) { AddItem(parent, shape); } else AddItem(GetRootItem(), shape); // initialize added shape if(initialize) { shape->CreateHandles(); if( m_pShapeCanvas ) { shape->SetHoverColour(m_pShapeCanvas->GetHoverColour()); } if(HasChildren(shape)) { wxSFShapeBase* pChild; ShapeList lstChildren; // get shape's children (if exist) shape->GetChildShapes(sfANY, lstChildren, sfRECURSIVE); // initialize shape's children ShapeList::compatibility_iterator node = lstChildren.GetFirst(); while(node) { pChild = (wxSFShapeBase*)node->GetData(); pChild->CreateHandles(); pChild->Update(); if( m_pShapeCanvas ) { pChild->SetHoverColour(m_pShapeCanvas->GetHoverColour()); } node = node->GetNext(); } } } // reset scale of assigned shape canvas (if exists and it is necessary...) if( m_pShapeCanvas && shape->IsKindOf( CLASSINFO(wxSFControlShape) ) ) { m_pShapeCanvas->SetScale( 1 ); } if( m_pShapeCanvas ) { if( saveState ) { m_pShapeCanvas->SaveCanvasState(); } } if( err ) *err = wxSF::errOK; } else { //wxMessageBox(wxString::Format(wxT("Unable to add '%s' class object to the canvas"), shape->GetClassInfo()->GetClassName()), wxT("ShapeFramework"), wxICON_WARNING); delete shape; shape = NULL; if( err ) *err = wxSF::errNOT_ACCEPTED; } } else if( err ) *err = wxSF::errINVALID_INPUT; return shape; } wxSFShapeBase* wxSFDiagramManager::CreateConnection(long srcId, long trgId, bool saveState, wxSF::ERRCODE *err) { return CreateConnection(srcId, trgId, CLASSINFO(wxSFLineShape), saveState, err); } wxSFShapeBase* wxSFDiagramManager::CreateConnection(long srcId, long trgId, wxClassInfo *lineInfo, bool saveState, wxSF::ERRCODE *err) { wxSFShapeBase* pShape = AddShape(lineInfo, sfDONT_SAVE_STATE, err); if(pShape) { wxSFLineShape *pLine = (wxSFLineShape*)pShape; pLine->SetSrcShapeId(srcId); pLine->SetTrgShapeId(trgId); if( m_pShapeCanvas ) { if(saveState)m_pShapeCanvas->SaveCanvasState(); pLine->Refresh(); } } return pShape; } wxSFShapeBase* wxSFDiagramManager::CreateConnection(long srcId, long trgId, wxSFLineShape *line, bool saveState, wxSF::ERRCODE *err) { wxSFShapeBase* pShape = AddShape(line, NULL, wxDefaultPosition, sfINITIALIZE, sfDONT_SAVE_STATE, err); if(pShape) { wxSFLineShape *pLine = (wxSFLineShape*)pShape; pLine->SetSrcShapeId(srcId); pLine->SetTrgShapeId(trgId); if( m_pShapeCanvas ) { if(saveState)m_pShapeCanvas->SaveCanvasState(); pLine->Refresh(); } } return pShape; } void wxSFDiagramManager::RemoveShape(wxSFShapeBase* shape, bool refresh) { if(shape) { wxSFShapeBase *pParent = shape->GetParentShape(); // remove connected lines (to all children) ShapeList lstChildren; ShapeList lstConnections; ShapeList lstRemovedConnections; // get all shape's children shape->GetChildShapes(sfANY, lstChildren, sfRECURSIVE); lstChildren.Append(shape); // retrieve all assigned lines ShapeList::compatibility_iterator snode = lstChildren.GetFirst(); while(snode) { GetAssignedConnections(snode->GetData(), CLASSINFO(wxSFLineShape), wxSFShapeBase::lineBOTH, lstConnections); snode = snode->GetNext(); } // remove all assigne lines ShapeList::compatibility_iterator node = lstConnections.GetFirst(); while(node) { // one connection may be used by the parent and also by his child if(lstRemovedConnections.IndexOf(node->GetData()) == wxNOT_FOUND) { lstRemovedConnections.Append(node->GetData()); RemoveShape(node->GetData(), false); } node = node->GetNext(); } // remove the shape also from m_lstCurrentShapes list if( m_pShapeCanvas ) m_pShapeCanvas->RemoveFromTemporaries( shape ); // remove the shape RemoveItem(shape); if( pParent ) pParent->Update(); if( refresh && m_pShapeCanvas ) m_pShapeCanvas->Refresh(false); } } void wxSFDiagramManager::RemoveShapes(const ShapeList& selection) { wxSFShapeBase* pShape; ShapeList::compatibility_iterator node = selection.GetFirst(); while(node) { pShape = node->GetData(); // it is important to check whether double-linked shapes already exist before // they are deleted if(Contains(pShape))RemoveShape(pShape, false); node = node->GetNext(); } } void wxSFDiagramManager::Clear() { RemoveAll(); if( m_pShapeCanvas ) { m_pShapeCanvas->GetMultiselectionBox().Show(false); m_pShapeCanvas->UpdateVirtualSize(); } } //----------------------------------------------------------------------------------// // Serialization/deserialization functions //----------------------------------------------------------------------------------// bool wxSFDiagramManager::SerializeToXml(const wxString& file, bool withroot) { return wxXmlSerializer::SerializeToXml(file, withroot); } bool wxSFDiagramManager::SerializeToXml(wxOutputStream& outstream, bool withroot) { return wxXmlSerializer::SerializeToXml(outstream, withroot); } bool wxSFDiagramManager::DeserializeFromXml(const wxString& file) { bool fSuccess = false; wxFileInputStream instream(file); if(instream.IsOk()) { if( m_pShapeCanvas) m_pShapeCanvas->ClearCanvasHistory(); fSuccess = DeserializeFromXml(instream); if( m_pShapeCanvas) m_pShapeCanvas->SaveCanvasState(); } else wxMessageBox(wxT("Unable to initialize input stream."), wxT("ShapeFramework"), wxOK | wxICON_ERROR); return fSuccess; } bool wxSFDiagramManager::DeserializeFromXml(wxInputStream& instream) { // load an XML file try { wxXmlDocument xmlDoc; xmlDoc.Load(instream); wxXmlNode* root = xmlDoc.GetRoot(); if(root && (root->GetName() == wxT("chart"))) { // read shape objects from XML recursively DeserializeObjects(NULL, root); return true; } else wxMessageBox(wxT("Unknown file format."), wxT("ShapeFramework"), wxOK | wxICON_WARNING); } catch (...) { wxMessageBox(wxT("Unable to load XML file."), wxT("ShapeFramework"), wxOK | wxICON_ERROR); } return false; } void wxSFDiagramManager::DeserializeObjects(xsSerializable* parent, wxXmlNode* node) { _DeserializeObjects(parent, node); // update IDs in connection lines and grids UpdateConnections(); UpdateGrids(); m_lstIDPairs.Clear(); if( m_pShapeCanvas ) { //m_pShapeCanvas->MoveShapesFromNegatives(); m_pShapeCanvas->UpdateVirtualSize(); } } void wxSFDiagramManager::_DeserializeObjects(xsSerializable* parent, wxXmlNode* node) { wxSFShapeBase *pShape; wxXS::IntArray arrNewIDs; SerializableList lstForUpdate; wxXmlNode* shapeNode = node->GetChildren(); while(shapeNode) { if(shapeNode->GetName() == wxT("object")) { #if wxVERSION_NUMBER < 2900 pShape = AddShape((wxSFShapeBase*)wxCreateDynamicObject(shapeNode->GetPropVal(wxT("type"), wxT(""))), parent, wxPoint(0, 0), true, sfDONT_SAVE_STATE); #else pShape = AddShape((wxSFShapeBase*)wxCreateDynamicObject(shapeNode->GetAttribute(wxT("type"), wxT(""))), parent, wxPoint(0, 0), true, sfDONT_SAVE_STATE); #endif if(pShape) { // store new assigned ID lstForUpdate.Append( pShape ); pShape->GetChildrenRecursively( NULL, lstForUpdate ); for( SerializableList::iterator it = lstForUpdate.begin(); it != lstForUpdate.end(); ++it ) { arrNewIDs.Add( (*it)->GetId() ); } // deserialize stored content pShape->DeserializeObject(shapeNode); // update handle in line shapes if( pShape->IsKindOf( CLASSINFO(wxSFLineShape) ) ) { pShape->CreateHandles(); m_lstLinesForUpdate.Append(pShape); } else if( pShape->IsKindOf( CLASSINFO(wxSFGridShape) ) ) { m_lstGridsForUpdate.Append(pShape); } // store information about IDs' changes and re-assign shapes' IDs int newId, i = 0; for( SerializableList::iterator it = lstForUpdate.begin(); it != lstForUpdate.end(); ++it ) { newId = arrNewIDs[i++]; if( newId != (*it)->GetId() ) { m_lstIDPairs.Append( new IDPair((*it)->GetId(), newId) ); (*it)->SetId( newId ); } } // deserialize child objects _DeserializeObjects(pShape, shapeNode); arrNewIDs.Clear(); lstForUpdate.Clear(); } else { // there are some unsupported shapes so the diagrams must be cleared because of possible damage RemoveAll(); m_lstLinesForUpdate.Clear(); m_lstGridsForUpdate.Clear(); wxMessageBox( wxT("Deserialization couldn't be completed because not of all shapes are accepted."), wxT("wxShapeFramework"), wxOK | wxICON_WARNING ); return; } } else if(shapeNode->GetName() == m_sRootName + wxT("_properties")) { m_pRoot->DeserializeObject(shapeNode->GetChildren()); } shapeNode = shapeNode->GetNext(); } } //----------------------------------------------------------------------------------// // Shape handling functions //----------------------------------------------------------------------------------// void wxSFDiagramManager::AcceptShape(const wxString& type) { if(m_arrAcceptedShapes.Index(type) == wxNOT_FOUND) { m_arrAcceptedShapes.Add(type); } } bool wxSFDiagramManager::IsShapeAccepted(const wxString& type) { if( m_arrAcceptedShapes.Index(type) != wxNOT_FOUND )return true; else if( m_arrAcceptedShapes.Index(wxT("All")) != wxNOT_FOUND )return true; else return false; } void wxSFDiagramManager::GetAssignedConnections(wxSFShapeBase* parent, wxClassInfo* shapeInfo, wxSFShapeBase::CONNECTMODE mode, ShapeList& lines) { wxSFLineShape* pLine; if( parent->GetId() == -1 ) return; SerializableList lstLines; // lines are children of root item only so we have not to search recursively... GetRootItem()->GetChildren( shapeInfo, lstLines ); if( !lstLines.IsEmpty() ) { SerializableList::compatibility_iterator node = lstLines.GetFirst(); while(node) { pLine = (wxSFLineShape*)node->GetData(); switch(mode) { case wxSFShapeBase::lineSTARTING: if( pLine->GetSrcShapeId() == parent->GetId() ) lines.Append(pLine); break; case wxSFShapeBase::lineENDING: if( pLine->GetTrgShapeId() == parent->GetId() ) lines.Append(pLine); break; case wxSFShapeBase::lineBOTH: if( ( pLine->GetSrcShapeId() == parent->GetId() ) || ( pLine->GetTrgShapeId() == parent->GetId() ) ) lines.Append(pLine); break; } node = node->GetNext(); } } } void wxSFDiagramManager::GetShapes(wxClassInfo* shapeInfo, ShapeList& shapes, xsSerializable::SEARCHMODE mode) { GetItems(shapeInfo, (SerializableList&)shapes, mode); } wxSFShapeBase* wxSFDiagramManager::GetShapeAtPosition(const wxPoint& pos, int zorder, SEARCHMODE mode) { int nCounter = 0; ShapeList m_lstSortedShapes; wxSFShapeBase* pShape; // sort shapes list in the way that the line shapes will be at the top of the list ShapeList shapes; GetShapes(CLASSINFO(wxSFShapeBase), shapes, xsSerializable::searchBFS); ShapeList::compatibility_iterator node = shapes.GetFirst(); while(node) { pShape = node->GetData(); if(pShape->IsKindOf(CLASSINFO(wxSFLineShape))) { m_lstSortedShapes.Insert(pShape); nCounter++; } else m_lstSortedShapes.Insert(nCounter, pShape); node = node->GetNext(); } // find the topmost shape according to the given rules nCounter = 1; node = m_lstSortedShapes.GetFirst(); while(node) { pShape = (wxSFShapeBase*)node->GetData(); if(pShape->IsVisible() && pShape->IsActive() && pShape->Contains(pos)) { switch(mode) { case searchSELECTED: if(pShape->IsSelected()) { if(nCounter == zorder)return pShape; else nCounter++; } break; case searchUNSELECTED: if(!pShape->IsSelected()) { if(nCounter == zorder)return pShape; else nCounter++; } break; case searchBOTH: if(nCounter == zorder)return pShape; else nCounter++; break; } } node = node->GetNext(); } return NULL; } void wxSFDiagramManager::GetShapesAtPosition(const wxPoint& pos, ShapeList& shapes) { shapes.Clear(); wxSFShapeBase *pShape; ShapeList lstShapes; GetShapes(CLASSINFO(wxSFShapeBase), lstShapes); ShapeList::compatibility_iterator node = lstShapes.GetFirst(); while(node) { pShape = node->GetData(); if(pShape->IsVisible() && pShape->IsActive() && pShape->Contains(pos))shapes.Append(pShape); node = node->GetNext(); } } void wxSFDiagramManager::GetShapesInside(const wxRect& rct, ShapeList& shapes) { shapes.Clear(); wxSFShapeBase* pShape; ShapeList lstShapes; GetShapes(CLASSINFO(wxSFShapeBase), lstShapes); ShapeList::compatibility_iterator node = lstShapes.GetFirst(); while(node) { pShape = node->GetData(); if(pShape->IsVisible() && pShape->IsActive() && pShape->Intersects(rct))shapes.Append(pShape); node = node->GetNext(); } } wxSFShapeBase* wxSFDiagramManager::FindShape(long id) { if(id == -1)return NULL; else return (wxSFShapeBase*)GetItem(id); } void wxSFDiagramManager::GetNeighbours(wxSFShapeBase* parent, ShapeList& neighbours, wxClassInfo *shapeInfo, wxSFShapeBase::CONNECTMODE condir, bool direct) { if(parent) { parent->GetNeighbours(neighbours, shapeInfo, condir, direct); } else { wxASSERT(GetRootItem()); wxSFShapeBase* pShape; SerializableList::compatibility_iterator node = GetRootItem()->GetFirstChildNode(); while(node) { pShape = (wxSFShapeBase*)node->GetData(); pShape->GetNeighbours(neighbours, shapeInfo, condir, direct); node = node->GetNext(); } } } bool wxSFDiagramManager::HasChildren(wxSFShapeBase* parent) { if(parent->GetFirstChildNode())return true; else return false; } void wxSFDiagramManager::UpdateConnections() { if( !m_lstLinesForUpdate.IsEmpty() ) { wxSFLineShape* pLine; IDPair* pIDPair; // now check ids long oldSrcId, oldTrgId; long newSrcId, newTrgId; IDList::compatibility_iterator idnode; ShapeList::compatibility_iterator node = m_lstLinesForUpdate.GetFirst(); while(node) { pLine = (wxSFLineShape*)node->GetData(); newSrcId = oldSrcId = pLine->GetSrcShapeId(); newTrgId = oldTrgId = pLine->GetTrgShapeId(); idnode = m_lstIDPairs.GetFirst(); while(idnode) { pIDPair = idnode->GetData(); /*if(pIDPair->m_nNewID != pIDPair->m_nOldID) {*/ if(oldSrcId == pIDPair->m_nOldID) newSrcId = pIDPair->m_nNewID; if(oldTrgId == pIDPair->m_nOldID) newTrgId = pIDPair->m_nNewID; /*}*/ idnode = idnode->GetNext(); } pLine->SetSrcShapeId(newSrcId); pLine->SetTrgShapeId(newTrgId); // check whether line's src and trg shapes really exists if(!GetItem(pLine->GetSrcShapeId()) || !GetItem(pLine->GetTrgShapeId())) { RemoveItem(pLine); } node = node->GetNext(); } m_lstLinesForUpdate.Clear(); } } void wxSFDiagramManager::UpdateGrids() { if( !m_lstGridsForUpdate.IsEmpty() ) { // now check ids wxSFGridShape* pGrid; IDPair* pIDPair; int nIndex; IDList::compatibility_iterator idnode; ShapeList::compatibility_iterator node = m_lstGridsForUpdate.GetFirst(); while(node) { pGrid = (wxSFGridShape*)node->GetData(); nIndex = wxNOT_FOUND; idnode = m_lstIDPairs.GetFirst(); while(idnode) { pIDPair = idnode->GetData(); nIndex = pGrid->m_arrCells.Index( pIDPair->m_nOldID ); if( nIndex != wxNOT_FOUND ) pGrid->m_arrCells[ nIndex ] = pIDPair->m_nNewID; idnode = idnode->GetNext(); } // check whether grid's children really exists for( size_t i = 0; i < pGrid->m_arrCells.GetCount(); ) { if( !GetItem( pGrid->m_arrCells[i] ) ) pGrid->RemoveFromGrid( pGrid->m_arrCells[i] ); else i++; } node = node->GetNext(); } m_lstGridsForUpdate.Clear(); } } void wxSFDiagramManager::UpdateAll() { wxSFShapeBase *pShape; ShapeList lstShapes; GetShapes( CLASSINFO(wxSFShapeBase), lstShapes ); ShapeList::compatibility_iterator node = lstShapes.GetFirst(); while( node ) { pShape = node->GetData(); // update only shapes withour children because the Update() function is called recursively on all parents if( !HasChildren( pShape ) ) pShape->Update(); node = node->GetNext(); } } void wxSFDiagramManager::MoveShapesFromNegatives() { wxSFShapeBase *pShape; wxRealPoint shapePos; double minx = 0, miny = 0; // find the maximal negative position value ShapeList shapes; GetShapes(CLASSINFO(wxSFShapeBase), shapes); ShapeList::compatibility_iterator node = shapes.GetFirst(); while(node) { shapePos = node->GetData()->GetAbsolutePosition(); if(node == shapes.GetFirst()) { minx = shapePos.x; miny = shapePos.y; } else { if(shapePos.x < minx)minx = shapePos.x; if(shapePos.y < miny)miny = shapePos.y; } node = node->GetNext(); } // move all parents shape so they (and their children) will be located in the positive values only if((minx < 0) || (miny < 0)) { node = shapes.GetFirst(); while(node) { pShape = node->GetData(); if(pShape->GetParentShape() == NULL) { if(minx < 0)pShape->MoveBy(abs((int)minx), 0); if(miny < 0)pShape->MoveBy(0, abs((int)miny)); } node = node->GetNext(); } } }