From abbc64cf04a35ab3549d5c516f44c7c5921baa63 Mon Sep 17 00:00:00 2001 From: =?utf8?q?St=C3=A9phane=20Domas?= Date: Wed, 26 Apr 2017 14:17:46 +0200 Subject: [PATCH] 1st commit of all files --- .gitignore | 10 + AbstractBlock.cpp | 167 +++ AbstractBlock.h | 78 ++ AbstractBoxItem.cpp | 255 ++++ AbstractBoxItem.h | 112 ++ AbstractInterface.cpp | 279 ++++ AbstractInterface.h | 107 ++ ArithmeticEvaluator.cpp | 438 +++++++ ArithmeticEvaluator.h | 60 + BlockCategory.cpp | 47 + BlockCategory.h | 50 + BlockImplementation.cpp | 560 ++++++++ BlockImplementation.h | 68 + BlockLibraryTree.cpp | 137 ++ BlockLibraryTree.h | 50 + BlockLibraryWidget.cpp | 107 ++ BlockLibraryWidget.h | 45 + BlockParameter.cpp | 121 ++ BlockParameter.h | 68 + BlockParameterGeneric.cpp | 97 ++ BlockParameterGeneric.h | 46 + BlockParameterPort.cpp | 58 + BlockParameterPort.h | 42 + BlockParameterUser.cpp | 56 + BlockParameterUser.h | 57 + BlockParameterWishbone.cpp | 90 ++ BlockParameterWishbone.h | 58 + BlockWidget.cpp | 300 +++++ BlockWidget.h | 42 + BlocksToConfigureWidget.cpp | 77 ++ BlocksToConfigureWidget.h | 39 + BoxItem.cpp | 681 ++++++++++ BoxItem.h | 83 ++ ConnectedInterface.cpp | 68 + ConnectedInterface.h | 58 + ConnectionItem.cpp | 762 +++++++++++ ConnectionItem.h | 107 ++ Dispatcher.cpp | 832 ++++++++++++ Dispatcher.h | 89 ++ Exception.cpp | 39 + Exception.h | 80 ++ FunctionalBlock.cpp | 78 ++ FunctionalBlock.h | 49 + FunctionalInterface.cpp | 197 +++ FunctionalInterface.h | 59 + Graph.cpp | 31 + Graph.h | 35 + GroupBlock.cpp | 78 ++ GroupBlock.h | 47 + GroupInterface.cpp | 116 ++ GroupInterface.h | 54 + GroupItem.cpp | 581 +++++++++ GroupItem.h | 73 ++ GroupScene.cpp | 215 +++ GroupScene.h | 110 ++ GroupWidget.cpp | 279 ++++ GroupWidget.h | 85 ++ InterfaceItem.cpp | 445 +++++++ InterfaceItem.h | 88 ++ InterfacePropertiesWindow.cpp | 30 + InterfacePropertiesWindow.h | 22 + MainWindow.cpp | 399 ++++++ MainWindow.h | 128 ++ Makefile.in | 185 +++ Parameters.cpp | 1220 ++++++++++++++++++ Parameters.h | 171 +++ ParametersWindow.cpp | 72 ++ ParametersWindow.h | 34 + ReferenceBlock.cpp | 471 +++++++ ReferenceBlock.h | 69 + ReferenceInterface.cpp | 73 ++ ReferenceInterface.h | 48 + Toto.cpp | 12 + Toto.h | 35 + blast-tg.odt | Bin 0 -> 31472 bytes blast.config | 2 + blast.cpp | 18 + blast.creator | 1 + blast.creator.user | 193 +++ blast.files | 73 ++ blast.ico | Bin 0 -> 9662 bytes blast.includes | 15 + blast.qrc | 24 + blast.rc | 1 + blastconfig.xml | 26 + blastconfig.xsd | 210 +++ block-1I1O.xml | 34 + block-2I2O.xml | 36 + block-2INO.xml | 35 + block.xsd | 190 +++ doc/models-tg.pdf | Bin 0 -> 94799 bytes doc/models-tg.tex | 376 ++++++ icons/add_block.png | Bin 0 -> 3242 bytes icons/add_block.svg | 155 +++ icons/add_connection.png | Bin 0 -> 2112 bytes icons/add_connection.svg | 123 ++ icons/add_group.png | Bin 0 -> 3019 bytes icons/add_group.svg | 177 +++ icons/add_group_select.png | Bin 0 -> 3473 bytes icons/add_group_select.svg | 163 +++ icons/add_group_small.png | Bin 0 -> 1395 bytes icons/add_group_void.png | Bin 0 -> 3079 bytes icons/add_group_void.svg | 130 ++ icons/copy.png | Bin 0 -> 5112 bytes icons/cut.png | Bin 0 -> 29505 bytes icons/delete.png | Bin 0 -> 2540 bytes icons/edit_block.png | Bin 0 -> 3373 bytes icons/folder_add_16.png | Bin 0 -> 761 bytes icons/inter_add.png | Bin 0 -> 1822 bytes icons/link.png | Bin 0 -> 24685 bytes icons/load.png | Bin 0 -> 10501 bytes icons/new.ico | Bin 0 -> 168585 bytes icons/new_block.ico | Bin 0 -> 5939 bytes icons/new_block.png | Bin 0 -> 5344 bytes icons/open.png | Bin 0 -> 720 bytes icons/paste.png | Bin 0 -> 7402 bytes icons/redo-icon.png | Bin 0 -> 21414 bytes icons/save-as.png | Bin 0 -> 696 bytes icons/save.png | Bin 0 -> 8501 bytes icons/undo.png | Bin 0 -> 23352 bytes icons/window_new.png | Bin 0 -> 3266 bytes install.sh | 38 + install.sh.in | 38 + lib/README.txt | 119 ++ lib/implementations/apf27-wb-master_impl.xml | 69 + lib/implementations/demux_impl.xml | 71 + lib/implementations/impls.bmf | Bin 0 -> 8 bytes lib/implementations/multadd.vhd | 76 ++ lib/implementations/multadd_core.vhd | 76 ++ lib/implementations/multadd_ctrl.vhd | 154 +++ lib/implementations/multadd_impl.xml | 63 + lib/references/apf27-wb-master.xml | 46 + lib/references/demux.xml | 36 + lib/references/multadd.xml | 43 + lib/references/references.bmf | Bin 0 -> 5658 bytes object-files.txt | 47 + projectfile.xsd | 333 +++++ save/sauv.xml | 24 + save/test.xml | 69 + testproject.xml | 84 ++ 140 files changed, 15477 insertions(+) create mode 100644 .gitignore create mode 100644 AbstractBlock.cpp create mode 100644 AbstractBlock.h create mode 100644 AbstractBoxItem.cpp create mode 100644 AbstractBoxItem.h create mode 100644 AbstractInterface.cpp create mode 100644 AbstractInterface.h create mode 100644 ArithmeticEvaluator.cpp create mode 100644 ArithmeticEvaluator.h create mode 100644 BlockCategory.cpp create mode 100644 BlockCategory.h create mode 100644 BlockImplementation.cpp create mode 100644 BlockImplementation.h create mode 100644 BlockLibraryTree.cpp create mode 100644 BlockLibraryTree.h create mode 100644 BlockLibraryWidget.cpp create mode 100644 BlockLibraryWidget.h create mode 100644 BlockParameter.cpp create mode 100644 BlockParameter.h create mode 100644 BlockParameterGeneric.cpp create mode 100644 BlockParameterGeneric.h create mode 100644 BlockParameterPort.cpp create mode 100644 BlockParameterPort.h create mode 100644 BlockParameterUser.cpp create mode 100644 BlockParameterUser.h create mode 100644 BlockParameterWishbone.cpp create mode 100644 BlockParameterWishbone.h create mode 100644 BlockWidget.cpp create mode 100644 BlockWidget.h create mode 100644 BlocksToConfigureWidget.cpp create mode 100644 BlocksToConfigureWidget.h create mode 100644 BoxItem.cpp create mode 100644 BoxItem.h create mode 100644 ConnectedInterface.cpp create mode 100644 ConnectedInterface.h create mode 100644 ConnectionItem.cpp create mode 100644 ConnectionItem.h create mode 100644 Dispatcher.cpp create mode 100644 Dispatcher.h create mode 100644 Exception.cpp create mode 100644 Exception.h create mode 100644 FunctionalBlock.cpp create mode 100644 FunctionalBlock.h create mode 100644 FunctionalInterface.cpp create mode 100644 FunctionalInterface.h create mode 100644 Graph.cpp create mode 100644 Graph.h create mode 100644 GroupBlock.cpp create mode 100644 GroupBlock.h create mode 100644 GroupInterface.cpp create mode 100644 GroupInterface.h create mode 100644 GroupItem.cpp create mode 100644 GroupItem.h create mode 100644 GroupScene.cpp create mode 100644 GroupScene.h create mode 100644 GroupWidget.cpp create mode 100644 GroupWidget.h create mode 100644 InterfaceItem.cpp create mode 100644 InterfaceItem.h create mode 100644 InterfacePropertiesWindow.cpp create mode 100644 InterfacePropertiesWindow.h create mode 100644 MainWindow.cpp create mode 100644 MainWindow.h create mode 100644 Makefile.in create mode 100644 Parameters.cpp create mode 100644 Parameters.h create mode 100644 ParametersWindow.cpp create mode 100644 ParametersWindow.h create mode 100644 ReferenceBlock.cpp create mode 100644 ReferenceBlock.h create mode 100644 ReferenceInterface.cpp create mode 100644 ReferenceInterface.h create mode 100644 Toto.cpp create mode 100644 Toto.h create mode 100755 blast-tg.odt create mode 100755 blast.config create mode 100644 blast.cpp create mode 100755 blast.creator create mode 100755 blast.creator.user create mode 100755 blast.files create mode 100755 blast.ico create mode 100755 blast.includes create mode 100755 blast.qrc create mode 100755 blast.rc create mode 100644 blastconfig.xml create mode 100644 blastconfig.xsd create mode 100644 block-1I1O.xml create mode 100644 block-2I2O.xml create mode 100644 block-2INO.xml create mode 100644 block.xsd create mode 100644 doc/models-tg.pdf create mode 100644 doc/models-tg.tex create mode 100644 icons/add_block.png create mode 100644 icons/add_block.svg create mode 100644 icons/add_connection.png create mode 100644 icons/add_connection.svg create mode 100644 icons/add_group.png create mode 100644 icons/add_group.svg create mode 100644 icons/add_group_select.png create mode 100644 icons/add_group_select.svg create mode 100644 icons/add_group_small.png create mode 100644 icons/add_group_void.png create mode 100644 icons/add_group_void.svg create mode 100644 icons/copy.png create mode 100644 icons/cut.png create mode 100644 icons/delete.png create mode 100644 icons/edit_block.png create mode 100644 icons/folder_add_16.png create mode 100644 icons/inter_add.png create mode 100644 icons/link.png create mode 100644 icons/load.png create mode 100644 icons/new.ico create mode 100644 icons/new_block.ico create mode 100644 icons/new_block.png create mode 100644 icons/open.png create mode 100644 icons/paste.png create mode 100644 icons/redo-icon.png create mode 100644 icons/save-as.png create mode 100644 icons/save.png create mode 100644 icons/undo.png create mode 100644 icons/window_new.png create mode 100644 install.sh create mode 100644 install.sh.in create mode 100644 lib/README.txt create mode 100644 lib/implementations/apf27-wb-master_impl.xml create mode 100644 lib/implementations/demux_impl.xml create mode 100644 lib/implementations/impls.bmf create mode 100644 lib/implementations/multadd.vhd create mode 100644 lib/implementations/multadd_core.vhd create mode 100644 lib/implementations/multadd_ctrl.vhd create mode 100644 lib/implementations/multadd_impl.xml create mode 100644 lib/references/apf27-wb-master.xml create mode 100644 lib/references/demux.xml create mode 100644 lib/references/multadd.xml create mode 100644 lib/references/references.bmf create mode 100644 object-files.txt create mode 100644 projectfile.xsd create mode 100644 save/sauv.xml create mode 100644 save/test.xml create mode 100644 testproject.xml diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f44bef8 --- /dev/null +++ b/.gitignore @@ -0,0 +1,10 @@ +/archive/* +/build/* +/Makefile +/configure +*~ +*.aux +*.log +*.dvi +*.bbl +*.blg \ No newline at end of file diff --git a/AbstractBlock.cpp b/AbstractBlock.cpp new file mode 100644 index 0000000..6e9cc86 --- /dev/null +++ b/AbstractBlock.cpp @@ -0,0 +1,167 @@ +#include +#include +#include +#include "AbstractInterface.h" +#include "BlockParameter.h" + +AbstractBlock::AbstractBlock() { + name = ""; + parent = NULL; +} + +AbstractBlock::AbstractBlock(const QString& _name) { + name = _name; + parent = NULL; +} + +AbstractBlock::~AbstractBlock() { + + foreach(AbstractInterface* iface, inputs) { + delete iface; + } + foreach(AbstractInterface* iface, outputs) { + delete iface; + } + foreach(AbstractInterface* iface, bidirs) { + delete iface; + } + inputs.clear(); + outputs.clear(); + bidirs.clear(); + foreach(BlockParameter* p, params) { + delete p; + } + params.clear(); +} + +void AbstractBlock::setName(const QString& str) { + name = str; +} + +void AbstractBlock::setParent(AbstractBlock* _parent) { + parent = _parent; +} + +bool AbstractBlock::isReferenceBlock() { + return false; +} + +bool AbstractBlock::isFunctionalBlock() { + return false; +} + +bool AbstractBlock::isGroupBlock() { + return false; +} + +void AbstractBlock::addParameter(BlockParameter *param) { + params.append(param); +} + + +BlockParameter* AbstractBlock::getParameterFromName(QString name) { + foreach(BlockParameter* p, params) { + if (p->getName() == name) return p; + } + return NULL; +} + +void AbstractBlock::addInterface(AbstractInterface *inter) { + if(inter->getDirection() == AbstractInterface::Input){ + inputs.append(inter); + } else if(inter->getDirection() == AbstractInterface::Output){ + outputs.append(inter); + } else if(inter->getDirection() == AbstractInterface::InOut){ + bidirs.append(inter); + } +} + +void AbstractBlock::removeInterface(AbstractInterface *inter) { + /* CAUTION: no check is done about the connection state of this interface + Thus, if it is still connected to/from, there will be a crash + */ + if(inter->getDirection() == AbstractInterface::Input){ + inputs.removeAll(inter); + } else if(inter->getDirection() == AbstractInterface::Output){ + outputs.removeAll(inter); + } else if(inter->getDirection() == AbstractInterface::InOut){ + bidirs.removeAll(inter); + } + delete inter; +} + +void AbstractBlock::defineBlockParam(BlockParameter *param) +{ + cout << "definition of param : " << param->getName().toStdString() << endl; + bool ok = false; + QString value = QInputDialog::getText(NULL, "Block parameter", "value for the "+ param->getName() +" parameter of " + param->getOwner()->getName() + "?", QLineEdit::Normal, param->getValue().toString(), &ok); + + while (!ok && value.isEmpty()) + { + QMessageBox::critical(NULL, "Error", "You have to insert a value for the parameter or accept the default value !"); + value = QInputDialog::getText(NULL, "Block parameter", "value for the "+ param->getName() +" parameter of " + param->getOwner()->getName() + " ?", QLineEdit::Normal, param->getValue().toString(), &ok); + } + param->setValue(value); +} + +QList AbstractBlock::getInterfaces() { + QList list; + list.append(inputs); + list.append(outputs); + list.append(bidirs); + return list; +} + +AbstractInterface* AbstractBlock::getIfaceFromName(QString name) { + + foreach(AbstractInterface* iface, inputs) { + if (iface->getName() == name) return iface; + } + foreach(AbstractInterface* iface, outputs) { + if (iface->getName() == name) return iface; + } + foreach(AbstractInterface* iface, bidirs) { + if (iface->getName() == name) return iface; + } + return NULL; +} + +bool AbstractBlock::isWBConfigurable() { + + foreach(BlockParameter* p, params) { + if (p->isWishboneParameter()) return true; + } + return false; +} + +QList AbstractBlock::getUserParameters() { + QList lst; + foreach(BlockParameter* p, params) { + if (p->isUserParameter()) lst.append(p); + } + return lst; +} + +QList AbstractBlock::getGenericParameters() { + QList lst; + foreach(BlockParameter* p, params) { + if (p->isGenericParameter()) lst.append(p); + } + return lst; +} + +QList AbstractBlock::getPortParameters() { + QList lst; + foreach(BlockParameter* p, params) { + if (p->isPortParameter()) lst.append(p); + } + return lst; +} + +QList AbstractBlock::getWishboneParameters() { + QList lst; + foreach(BlockParameter* p, params) { + if (p->isWishboneParameter()) lst.append(p); + } + return lst; +} diff --git a/AbstractBlock.h b/AbstractBlock.h new file mode 100644 index 0000000..2c89cbf --- /dev/null +++ b/AbstractBlock.h @@ -0,0 +1,78 @@ +#ifndef __ABSTRACTBLOCK_H__ +#define __ABSTRACTBLOCK_H__ + +#include + +#include + +class AbstractInterface; +class BlockParameter; + +#define AB_TO_REF(ptr) ((ReferenceBlock*)ptr) +#define AB_TO_FUN(ptr) ((FunctionalBlock*)ptr) +#define AB_TO_GRP(ptr) ((GroupBlock*)ptr) + +using namespace std; +using namespace Qt; + +class AbstractBlock { + +public: + + AbstractBlock(); + AbstractBlock(const QString& _name); + virtual ~AbstractBlock(); + + // getters + inline QString getName() { return name; } + inline QList getParameters() { return params; } + inline QList getInputs() { return inputs; } + inline QList getOutputs() { return outputs; } + inline QList getBidirs() { return bidirs; } + QList getUserParameters(); + QList getGenericParameters(); + QList getPortParameters(); + QList getWishboneParameters(); + inline AbstractBlock* getParent() { return parent; } + // setters + void setName(const QString& str); + virtual void setParent(AbstractBlock* _parent); + + // testers + virtual bool isReferenceBlock(); + virtual bool isFunctionalBlock(); + virtual bool isGroupBlock(); + bool isWBConfigurable(); + + // others + virtual void parametersValidation(QList* checkedBlocks, QList* blocksToConfigure) = 0; // ugly but usefull + + void addParameter(BlockParameter *param); + void addInterface(AbstractInterface *inter); + void removeInterface(AbstractInterface *inter); + void defineBlockParam(BlockParameter *param); + + QList getInterfaces(); + AbstractInterface* getIfaceFromName(QString name); + BlockParameter* getParameterFromName(QString name); + +protected: + + + QString name; + + // parameters + QList params; + + // interfaces + QList inputs; + QList outputs; + QList bidirs; + + // others + + // NB: only GroupBlock and FunctionalBlock have a real parent + AbstractBlock* parent; +}; + +#endif // __ABSTRACTBLOCK_H__ diff --git a/AbstractBoxItem.cpp b/AbstractBoxItem.cpp new file mode 100644 index 0000000..0afbe29 --- /dev/null +++ b/AbstractBoxItem.cpp @@ -0,0 +1,255 @@ +#include "AbstractBoxItem.h" + +#include "Parameters.h" + +#include "Dispatcher.h" +#include "InterfaceItem.h" +#include "ConnectionItem.h" + +#include "AbstractBlock.h" +#include "GroupScene.h" +#include "AbstractInterface.h" +#include "ConnectedInterface.h" + + +AbstractBoxItem:: AbstractBoxItem(AbstractBlock *_refBlock, Dispatcher *_dispatcher, Parameters *_params, QGraphicsItem *parent) : QGraphicsItem(parent) { + dispatcher = _dispatcher; + params = _params; + refBlock = _refBlock; + QFont fontId("Arial",10); + QFontMetrics fmId(fontId); + nameWidth = fmId.width(refBlock->getName()); + nameHeight = fmId.height(); + nameMargin = 10; + ifaceMargin = 10; + + // the six following values will be override in subclass constructors + minimumBoxWidth = 0; + minimumBoxHeight = 0; + boxWidth = 0; + boxHeight = 0; + totalWidth = 0; + totalHeight = 0; + + originPoint = QPointF(0.0,0.0); + + selected = false; + currentInterface = NULL; + rstClkVisible = false; + + setAcceptHoverEvents(true); + + // NOTE : initInterfaces() is only called in subclasses +} + +AbstractBoxItem::~AbstractBoxItem() { + foreach(InterfaceItem* inter, interfaces) { + delete inter; + } + interfaces.clear(); +} + +bool AbstractBoxItem::isBoxItem() { + return false; +} + +bool AbstractBoxItem::isGroupItem() { + return false; +} + +void AbstractBoxItem::initInterfaces() +{ + /* TO DO : creating all needed InterfaceItem, with by default, input at west and output at east */ + int orientation = Parameters::West; + + foreach(AbstractInterface *inter, refBlock->getInterfaces()){ + if(inter->getPurpose() != AbstractInterface::Wishbone){ + InterfaceItem *item; + if(inter->getDirection() == AbstractInterface::Input){ + orientation = Parameters::West; + } else if(inter->getDirection() == AbstractInterface::Output){ + orientation = Parameters::East; + } else if(inter->getDirection() == AbstractInterface::InOut){ + orientation = Parameters::North; + } + item = new InterfaceItem(0.0 , orientation, (ConnectedInterface *)inter, this, params); + interfaces.append(item); + } + } +} + +InterfaceItem* AbstractBoxItem::searchInterfaceByName(QString name) { + foreach(InterfaceItem *inter, interfaces){ + if(inter->getName() == name) + return inter; + } + return NULL; +} + +InterfaceItem* AbstractBoxItem::searchInterfaceByRef(ConnectedInterface *ref) { + foreach(InterfaceItem *inter, interfaces){ + if(inter->refInter == ref) { + return inter; + } + } + return NULL; +} + +void AbstractBoxItem::addInterface(InterfaceItem *i, bool resetPosition) { + interfaces.append(i); + if (resetPosition) resetInterfacesPosition(); + updateGeometry(); + update(); +} + +void AbstractBoxItem::removeInterface(InterfaceItem *i) { + // NB : removing from model is done in dispatcher + interfaces.removeOne(i); + delete i; + + //resetInterfacesPosition(); + updateGeometry(); + update(); +} + + +void AbstractBoxItem::resetInterfacesPosition() { + + int nbNorth=0, nbSouth=0, nbEast=0, nbWest=0; + double cntNorth=1.0,cntSouth=1.0,cntEast=1.0,cntWest=1.0; + double positionRatio = 1.0; + + + foreach(InterfaceItem* inter, interfaces) { + // only data interfaces and if needed time and reset + if(inter->refInter->getPurpose() == AbstractInterface::Data || inter->getOwner()->isRstClkVisible()){ + if(inter->getOrientation() == Parameters::North){ + nbNorth++; + } else if(inter->getOrientation() == Parameters::South){ + nbSouth++; + } else if(inter->getOrientation() == Parameters::East){ + nbEast++; + } else if(inter->getOrientation() == Parameters::West){ + nbWest++; + } + } + } + + foreach(InterfaceItem* inter, interfaces) { + + if(inter->refInter->getPurpose() == AbstractInterface::Data || inter->getOwner()->isRstClkVisible()){ + + if(inter->getOrientation() == Parameters::North){ + positionRatio = cntNorth/(double)(nbNorth+1); + cntNorth += 1.0; + } else if(inter->getOrientation() == Parameters::South){ + positionRatio = cntSouth/(double)(nbSouth+1); + cntSouth += 1.0; + } else if(inter->getOrientation() == Parameters::East){ + positionRatio = cntEast/(double)(nbEast+1); + cntEast += 1.0; + } else if(inter->getOrientation() == Parameters::West){ + positionRatio = cntWest/(double)(nbWest+1); + cntWest += 1.0; + } + inter->setPositionRatio(positionRatio); + inter->updatePosition(); + } + } +} + +void AbstractBoxItem::deplaceInterface(QPointF pos) { + double positionRatio; + if(currentInterface->getOrientation() == Parameters::North || currentInterface->getOrientation() == Parameters::South){ + if(pos.x() < 0){ + positionRatio = 0; + if(pos.y() > 0 && pos.y() < boxHeight){ + currentInterface->setOrientation(Parameters::West); + } + } else if(pos.x() > boxWidth){ + positionRatio = 1; + if(pos.y() > 0 && pos.y() < boxHeight){ + currentInterface->setOrientation(Parameters::East); + } + } else { + positionRatio = ((double) pos.x())/boxWidth; + } + } else { + + if(pos.y() < 0){ + positionRatio = 0; + if(pos.x() > 0 && pos.x() < boxWidth){ + currentInterface->setOrientation(Parameters::North); + } + } else if(pos.y() > boxHeight){ + positionRatio = 1; + if(pos.x() > 0 && pos.x() < boxWidth){ + currentInterface->setOrientation(Parameters::South); + } + } else { + positionRatio = ((double) pos.y())/boxHeight; + } + } + currentInterface->setPositionRatio(positionRatio); + currentInterface->updatePosition(); +} + +QRectF AbstractBoxItem::boundingRect() const { + // returns a QRectF that contains the block (i.e the main rectangle, interfaces, title, ...) + QPointF p = originPoint - QPointF(nameHeight,nameHeight); + QSizeF s(totalWidth+2*nameHeight,totalHeight+2*nameHeight); + return QRectF(p,s); +} + + +/* isInterface() : return true if there are some interfaces + with the given orientation (N,S,E,O) +*/ +bool AbstractBoxItem::isInterfaces(int orientation) const { + foreach(InterfaceItem* inter, interfaces) { + if (inter->getOrientation() == orientation) return true; + } + return false; +} + +int AbstractBoxItem::nbInterfacesByOrientation(int orientation) { + int nb = 0; + foreach(InterfaceItem* inter, interfaces) { + if ((inter->visible) && (inter->getOrientation() == orientation)) nb++; + } + return nb; +} + +void AbstractBoxItem::updateInterfacesAndConnections() { + + // update all interfaces positions + foreach(InterfaceItem *item, interfaces){ + item->updatePosition(); + } + if (getScene() != NULL) { + // update all connections from/to this block + foreach(ConnectionItem *item, getScene()->getConnectionItems()){ + if ((item->getFromInterfaceItem()->getOwner() == this) || (item->getToInterfaceItem()->getOwner() == this)) { + item->setPathes(); + } + } + } +} + +void AbstractBoxItem::setDimension(int x, int y) { + boxWidth = x; + boxHeight = y; +} + +InterfaceItem* AbstractBoxItem::getInterfaceFromCursor(qreal x, qreal y) { + + foreach(InterfaceItem* inter, interfaces) { + if(x > inter->boundingRect().x() && x < (inter->boundingRect().x() + inter->boundingRect().width())){ + if(y > inter->boundingRect().y() && y < (inter->boundingRect().y() + inter->boundingRect().height())){ + return inter; + } + } + } + /* TO DO : check each interfaces if it contains x,y. If yes, return that interface */ + return NULL; +} diff --git a/AbstractBoxItem.h b/AbstractBoxItem.h new file mode 100644 index 0000000..6eeb46c --- /dev/null +++ b/AbstractBoxItem.h @@ -0,0 +1,112 @@ +#ifndef __ABSTRACTBOXITEM_H__ +#define __ABSTRACTBOXITEM_H__ + +#include + +#include +#include +#include + +class Dispatcher; +class InterfaceItem; +class Parameters; +class AbstractBlock; +class GroupScene; +class ConnectedInterface; + +class AbstractBoxItem : public QGraphicsItem { + +public: + + enum BorderType { NoBorder = 0, BorderEast, BorderNorth, BorderWest, BorderSouth, CornerSouthEast, Title}; + enum ChangeType { Resize = 0, InterfaceMove }; + + AbstractBoxItem(AbstractBlock *_refBlock, Dispatcher *_dispatcher, Parameters *_params, QGraphicsItem* parent = Q_NULLPTR); + + virtual ~AbstractBoxItem(); + + // getters + inline AbstractBlock* getRefBlock() { return refBlock; } + inline int getWidth() { return boxWidth;} + inline int getHeight() { return boxHeight;} + inline int getTotalWidth() { return totalWidth;} + inline int getTotalHeight() { return totalHeight; } + inline QList getInterfaces() { return interfaces; } + inline InterfaceItem *getCurrentInterface() { return currentInterface; } + inline int getId(){ return id; } + inline GroupScene* getScene() { return (GroupScene*)(scene()); } + inline int getIfaceMargin() { return ifaceMargin; } + inline int getNameMargin() { return nameMargin; } + inline QPointF getOriginPoint() { return originPoint; } + + // setters + inline void setId(int id){ this->id = id; } + inline void setSelected(bool _selected) { selected = _selected; } + inline void setRstClkVisible(bool b){ rstClkVisible = b;} + void setDimension(int x, int y); + inline void setCurrentInterface(InterfaceItem* iface) { currentInterface = iface; } + + // testers + virtual bool isBoxItem(); + virtual bool isGroupItem(); + inline bool isSelected() { return selected; } + inline bool isRstClkVisible(){ return rstClkVisible;} + bool isInterfaces(int orientation) const; + + // others + + void addInterface(InterfaceItem* i, bool resetPosition = false); + void removeInterface(InterfaceItem* i); + void resetInterfacesPosition(); + void deplaceInterface(QPointF pos); + void updateInterfacesAndConnections(); + + InterfaceItem *searchInterfaceByName(QString name); + InterfaceItem *searchInterfaceByRef(ConnectedInterface* ref); + InterfaceItem* getInterfaceFromCursor(qreal x, qreal y); + +protected: + Dispatcher *dispatcher; + Parameters *params; + QList interfaces; + /* NOTE : the reference block may be a FunctionalBlock or a GroupBlock, depending on the fact + that the real instace will be of FunctionalBlock or GroupBlock + */ + AbstractBlock *refBlock; + + InterfaceItem* currentInterface; // currently clicked interface in ItemEdition mode + + BorderType currentBorder; // which border cursor is on + QPointF cursorPosition; + + int id; + int boxWidth; // the width of the main box (without interface, title, ...) + int boxHeight; // the height of the main box (without interface, title, ...) + int minimumBoxWidth; // minimum width of the main box: may be recomputed if position/number of interface changes + int minimumBoxHeight; // minimum height of the main box: may be recomputed if position/number of interface changes + int totalWidth; // width and heigth taking into account interfaces,title, ... + int totalHeight; + int nameWidth; // the width of the box (group or block) name in Arial 10 + int nameHeight; // the height of the name in Arial 10 + int nameMargin; // the margin around each side of the name + int ifaceMargin; // the margin around each side of interfaces' name + QPointF originPoint; // the left-top point that is the origin of the bounding box + + bool selected; + bool rstClkVisible; + + QPointF currentPosition; // the start point for resize + + virtual void updateMinimumSize() = 0; // modify the minimum size + virtual bool updateGeometry(ChangeType type) = 0; // modify the originPoint and the total dimension + + QRectF boundingRect() const; + /* pure virtual method inherited from QGraphicsItem : + virtual void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = 0) =0; + virtual QRectF boundingRect() const =0; + */ + void initInterfaces(); + int nbInterfacesByOrientation(int orientation); +}; + +#endif // __ABSTRACTBOXITEM_H__ diff --git a/AbstractInterface.cpp b/AbstractInterface.cpp new file mode 100644 index 0000000..25cd2d3 --- /dev/null +++ b/AbstractInterface.cpp @@ -0,0 +1,279 @@ +#include "AbstractInterface.h" +#include "BlockParameterPort.h" +#include "AbstractBlock.h" + +AbstractInterface::AbstractInterface(AbstractBlock* _owner) { + + owner = _owner; + name = ""; + width = "1"; + direction = Input; + purpose = Data; + level = Basic; + type = Boolean; + +} + +AbstractInterface::AbstractInterface(AbstractBlock* _owner, const QString& _name, const QString& _type, const QString& _width, int _direction, int _purpose, int _level) { + + owner = _owner; + name = _name; + width = _width; + direction = _direction; + purpose = _purpose; + level = _level; + if (direction == InOut) { + level = Top; + } + type = typeFromString(_type); +} + +AbstractInterface::AbstractInterface(AbstractInterface* other) { + owner = NULL; + name = other->name; + type = other->type; + width = other->width; + direction = other->direction; + purpose = other->purpose; + level = other->level; +} + +AbstractInterface::~AbstractInterface() { + +} + +bool AbstractInterface::isReferenceInterface() { + return false; +} + +bool AbstractInterface::isFunctionalInterface() { + return false; +} + +bool AbstractInterface::isGroupInterface() { + return false; +} + +QString AbstractInterface::getPurposeString() { + QString str; + switch(purpose){ + case AbstractInterface::Data: + str = QString("data"); + break; + case AbstractInterface::Clock: + str = QString("clock"); + break; + case AbstractInterface::Reset: + str = QString("reset"); + break; + case AbstractInterface::Wishbone: + str = QString("wishbone"); + break; + } + return str; +} + +QString AbstractInterface::getDirectionString() { + QString str; + switch(direction){ + case AbstractInterface::Input: + str = QString("input"); + break; + case AbstractInterface::Output: + str = QString("output"); + break; + case AbstractInterface::InOut: + str = QString("inout"); + break; + } + return str; +} + +QString AbstractInterface::getLevelString() { + QString str; + switch(level){ + case AbstractInterface::Basic: + str = QString("basic"); + break; + case AbstractInterface::Top: + str = QString("top"); + break; + } + return str; +} + +double AbstractInterface::getDoubleWidth() throw(QException) { + + static QString fctName = "AbstractInterface::getDoubleWidth()"; + #ifdef DEBUG_FCTNAME + cout << "call to " << qPrintable(fctName) << endl; + #endif + + /* + cout << "start AbstractInterface::getDoubleWidth()" << endl; + bool ok; + double width = getWidth().toDouble(&ok); + + if(!ok){ + ArithmeticEvaluator *evaluator = new ArithmeticEvaluator; + cout << "evaluator created!" << endl; + evaluator->setExpression(getWidth()); + cout << "expression defined!" << endl; + foreach(BlockParameter *param, getOwner()->getParameters()){ + evaluator->setVariableValue(param->getName(), param->getIntValue()); + cout << "param : " << param->getName().toStdString() << " evaluated!" << endl; + } + width = evaluator->evaluate(); + cout << "expression evaluated succefully!" << endl; + } + cout << "real width : " << width << endl; + return width; + */ + + return 1.0; +} + +void AbstractInterface::setPurpose(int _purpose) { + if ((_purpose>=Data) && (_purpose <= Wishbone)) { + purpose = _purpose; + } +} + +void AbstractInterface::setDirection(int _direction) { + if ((_direction > Input) && (_direction <= InOut)) { + direction = _direction; + } + if (direction == InOut) { + level = Top; + } +} + +void AbstractInterface::setLevel(int _level) { + if ((_level >= Basic) << (_level < Top)) { + level = _level; + } + if (direction == InOut) { + level = Top; + } +} + + + +int AbstractInterface::getIntDirection(QString str) +{ + if(str == "input") return Input; + if(str == "output") return Output; + if(str == "inOut") return InOut; + return -1; +} + +int AbstractInterface::getIntLevel(QString str) +{ + if(str == "basic") return Basic; + if(str == "top") return Top; + return -1; +} + +QString AbstractInterface::getTypeString() { + + if (type == Boolean) { + return "boolean"; + } + else if (type == Natural) { + return "natural"; + } + else if (type == Expression) { + return "expression"; + } + return "invalid_type"; +} + +int AbstractInterface::typeFromString(const QString &_type) { + + int ret; + if (_type == "expression") { + ret = Expression; + } + else if (_type == "boolean") { + ret = Boolean; + } + else if (_type == "natural") { + ret = Natural; + } + return ret; +} + +QString AbstractInterface::toVHDL(int context, int flags) throw(Exception) { + + if (isReferenceInterface()) throw(Exception(IFACE_INVALID_TYPE)); + + QString msb = width; + QString ret=""; + bool ok; + if ((context == BlockParameter::Entity) || (context == BlockParameter::Component)) { + + QString formatBool = "%1 : %2 std_logic"; + QString formatVector = "%1 : %2 std_logic_vector(%3 downto %4)"; + if ((flags & BlockParameter::NoComma) == 0) { + formatBool.append(";"); + formatVector.append(";"); + } + QString orientation=""; + if (direction == Input) { + orientation = "in"; + } + else if (direction == Output) { + orientation = "out"; + } + else { + orientation = "inout"; + } + if (type == Boolean) { + ret = formatVector.arg(name).arg(orientation); + } + else if (type == Natural) { + int w = width.toInt(&ok); + if (!ok) { + throw(Exception(INVALID_VALUE)); + } + else { + w -= 1; + ret = formatVector.arg(name).arg(orientation).arg(w).arg("0"); + } + } + else if (type == Expression) { + /* must check the following conditions : + - if it contains user/port parameters : must evaluate their numeric value + - if it contains generic parameters : just remove the $ -> the expression is not arithmetically evaluated. + */ + QList listGenerics = owner->getGenericParameters(); + QList listUsers = owner->getUserParameters(); + QList listPorts = owner->getPortParameters(); + foreach(BlockParameter* p, listUsers) { + QString var = "$"; + var.append(p->getName()); + if (width.contains(var)) { + int w = p->getValue().toInt(&ok); + if (!ok) throw(Exception(INVALID_VALUE)); + msb.replace(var,p->getValue().toString()); + } + } + foreach(BlockParameter* p, listPorts) { + QString var = "$"; + var.append(p->getName()); + if (width.contains(var)) { + BlockParameterPort* pp = (BlockParameterPort*)p; + AbstractInterface* iface = owner->getIfaceFromName(pp->getIfaceName()); + + int w = p->getValue().toInt(&ok); + if (!ok) throw(Exception(INVALID_VALUE)); + msb.replace(var,p->getValue().toString()); + } + } + + ret = formatVector.arg(name).arg(orientation).arg("toto").arg("0"); + } + } + return ret; +} + diff --git a/AbstractInterface.h b/AbstractInterface.h new file mode 100644 index 0000000..78414c3 --- /dev/null +++ b/AbstractInterface.h @@ -0,0 +1,107 @@ +#ifndef __ABSTRACTINTERFACE_H__ +#define __ABSTRACTINTERFACE_H__ + +#include + +#include +#include + +class AbstractBlock; + +#include "Exception.h" +class Exception; + +#define AI_TO_REF(ptr) ((ReferenceInterface*)ptr) +#define AI_TO_FUN(ptr) ((FunctionalInterface*)ptr) +#define AI_TO_GRP(ptr) ((GroupInterface*)ptr) + +using namespace std; +using namespace Qt; + + +class AbstractInterface { + +public : + + enum IfaceWidthType { Expression = 1, Boolean, Natural}; + enum IfacePurpose { Data = 1, Clock = 2, Reset = 3, Wishbone = 4 }; + enum IfaceDirection { Input = 1, Output = 2, InOut = 3 }; + enum IfaceLevel { Basic = 1, Top = 2 }; + enum IfaceVHDLContext { Entity = 1, Component = 2, Architecture = 3 }; // NB : 3 is when creating an instance of the block that owns this iface + enum IfaceVHDLFlags { NoComma = 1 }; + + static int getIntDirection(QString str); + static int getIntLevel(QString str); + + AbstractInterface(AbstractBlock* _owner); + AbstractInterface(AbstractBlock* _owner, const QString& _name, const QString& _type, const QString& _width, int _direction, int _purpose, int _level); + AbstractInterface(AbstractInterface* other); + virtual ~AbstractInterface(); + + // getters + inline QString getName() { return name;} + inline int getType() { return type; } + QString getTypeString(); + inline QString getWidth() { return width;} + inline int getPurpose() { return purpose;} + QString getPurposeString(); + inline int getDirection() { return direction;} + QString getDirectionString(); + inline int getLevel() { return level;} + QString getLevelString(); + inline AbstractBlock *getOwner() { return owner;} + + double getDoubleWidth() throw(QException); + + //virtual QList getConnectedTo() = 0; + + /* NB: only GroupInterface and FunctionalInterface have a connectedFrom, so + defining getConnectedFrom as pure virtual is normal, usefull even though it is ugly :-) + */ + virtual AbstractInterface* getConnectedFrom() = 0; + + // setters + inline void setOwner(AbstractBlock* _owner) { owner = _owner; } + inline void setName(const QString& _name) { name = _name; } + inline void setWidth(const QString& _width) { width = _width; } + inline void setType(int _type) { type = _type;} + inline void setType(const QString& _type) { type = typeFromString(_type);} + void setPurpose(int _purpose); + void setDirection(int _direction); + void setLevel(int _level); + + // testers + virtual bool isReferenceInterface(); + virtual bool isFunctionalInterface(); + virtual bool isGroupInterface(); + //virtual bool isConnectedTo() = 0; + //virtual bool isConnectedFrom() = 0; + //virtual bool canConnectTo(AbstractInterface* iface) = 0; // returns yes if this can be connected to iface, no if not + //virtual bool canConnectFrom(AbstractInterface* iface) = 0; // returns yes if this can be connected from iface, no if not + + // others + virtual AbstractInterface *clone() = 0; + + //virtual bool addConnectedTo(AbstractInterface *inter) = 0; + //virtual void removeConnectedTo(AbstractInterface *inter) = 0; + //virtual bool setConnectedFrom(AbstractInterface* inter) = 0; + //virtual void clearConnectedTo() = 0; + //virtual void clearConnections() = 0; + //virtual void connectionsValidation(QStack *interfacetoValidate, QList *validatedInterfaces) throw(Exception) = 0; + int typeFromString(const QString &_type); + + QString toVHDL(int context, int flags) throw(Exception); + +protected: + QString name; + int type; + QString width; + int purpose; + int direction; + int level; + + AbstractBlock* owner; +}; + + +#endif // __ABSTRACTINTERFACE_H__ diff --git a/ArithmeticEvaluator.cpp b/ArithmeticEvaluator.cpp new file mode 100644 index 0000000..60db08e --- /dev/null +++ b/ArithmeticEvaluator.cpp @@ -0,0 +1,438 @@ +/*-==============================================================- + +file : ArithmeticEvaluator.cpp + +creation date : 19/05/2015 + +author : S. Domas (sdomas@univ-fcomte.fr) + +description : + +supp. infos : saved in UTF-8 [éè] + +-==============================================================-*/ +#include "ArithmeticEvaluator.h" +#include + +ArithmeticEvaluator::ArithmeticEvaluator() { + opMarkers = "+-*/"; + varMarkers = "$"; + expression = QStringList(); + /* CAUTION : function are mandatory using ( ) to encapsulate the operand + since spaces are removed. Thus, an expression like sin 10 will lead to + sin10 after spaces removal, and thus becomes invalid. + */ + fctMarkers << "sin" << "cos" << "log10" << "log2" << "log" << "ceil" << "floor" << "round"; +} + +ArithmeticEvaluator::ArithmeticEvaluator(const QString& _expression) throw(int) { + opMarkers = "+-*/"; + varMarkers = "$"; + fctMarkers << "sin" << "cos" << "log10" << "log2" << "log" << "ceil" << "floor" << "round"; + try { + setExpression(_expression); + } + catch(int e) { + throw(e); + } +} + +void ArithmeticEvaluator::setExpression(const QString& _expression) throw(int) { + try { + convert(_expression); + } + catch(int e) { + throw(e); + } +} + +void ArithmeticEvaluator::print() { + foreach(QString elt, expression) { + cout << qPrintable(elt) << " "; + } + cout << endl; +} + +double ArithmeticEvaluator::evalFunction(int indexFunc, double value) { + double res = 0.0; + if (indexFunc == 0) { + res = sin(value); + } + else if (indexFunc == 1) { + res = cos(value); + } + else if (indexFunc == 2) { + res = log10(value); + } + else if (indexFunc == 3) { + res = log2(value); + } + else if (indexFunc == 4) { + res = log(value); + } + else if (indexFunc == 5) { + res = ceil(value); + } + else if (indexFunc == 6) { + res = floor(value); + } + else if (indexFunc == 7) { + res = round(value); + } + + return res; +} + +double ArithmeticEvaluator::evaluate() throw(int) { + QStack stack; + bool ok; + double value1,value2; + int index = 0; + QChar c; + foreach(QString elt, expression) { + c = elt.at(0); + /* CAUTION : + \x1bn, n must correspond to the order of QString in fctMarkers. + */ + if (c == '\x1b') { + value1 = stack.pop(); + elt.remove(0,1); + int idFunc = elt.toInt(&ok); + if ((!ok) || (idFunc < 0) || (idFunc >= fctMarkers.size())) throw(-index); + stack.push(evalFunction(idFunc,value1)); + } + else if (varMarkers.contains(c)) { + if (!varValues.contains(elt)) throw(-index); + stack.push(varValues.value(elt)); + } + else if (opMarkers.contains(c)) { + value2 = stack.pop(); + value1 = stack.pop(); + if (c == '+') { + stack.push(value1+value2); + } + else if (c == '-') { + stack.push(value1-value2); + } + else if (c == '*') { + stack.push(value1*value2); + } + else if (c == '/') { + stack.push(value1/value2); + } + } + else { + value1 = elt.toDouble(&ok); + if (!ok) throw(-index); + stack.push(value1); + } + index += 1; + } + + value1 = stack.pop(); + if (!stack.isEmpty()) throw(-1); + return value1; +} + +/* NOTE : + expr is of form: + ([ value1 | var1 | func1 | expr1 ] op1 [ value2 | var2 | func2 | expr2 ] op2 ... [ valuen | varn | funcn | exprn ]) + + Thus, if the whole expression does not end with ), we encapsulate it with ( ). + + If we don't consider priority among operators, then expression is converted into + A1 A2 op1 A3 op2 ... An opn-1 + + example : 1+2+3-4-5 -> 1 2 + 3 + 4 - + + If there is priority : * > / > + or - or func, then it is more complex + + example : 1+2+3/4*5-6 -> 1 2 + 3 4 5 * / + 6 - + + with parenthesis, we can do the same recursively + + example : 1+(2+3/4)*5-6 = 1 + expr1 * 5 - 6 -> 1 expr1 5 * + 6 - + but expr1 -> 2 3 4 / +, then we finally have 1 2 3 4 / + 5 * + 6 - + + a func is of form: + func_name(expr) + + example : ((1+3-sin(5/6))*(4+(7/3))) + + recursive cross in-depth of the expression leads to do a recursive call each time a ( is encountered. + Return of the recursive call is done when ) is encountered. + + */ + +void ArithmeticEvaluator::convert(const QString& _expression) throw(int) { + QString expr = _expression; + QString result=""; + expr.remove(QChar(' '), Qt::CaseInsensitive); + foreach(QString func, fctMarkers) { + QString rep = QString("\x1b%1").arg(fctMarkers.indexOf(QRegExp(func))); + + expr.replace(QRegExp(func),rep); + } + //cout << "packed expr: " << qPrintable(expr) << endl; + + int offset = 0; + try { + result = convertRecur(expr,&offset); + expression = result.split(","); + } + catch(int err) { + cerr << "error while recursive conversion: "; + throw(err); + } + +} + +QString ArithmeticEvaluator::convertRecur(const QString& _expression, int *offset) throw(int) { + QString result=""; + QStack pile; + + int size; + QChar c; + + QString number; + QString expr = _expression; + + // testing if it starts by a (,number, variable or function + if (!checkAfterOp(expr,*offset-1)) throw(*offset); + + // testing if it starts with a number + if ((expr[*offset] == '-') || (expr[*offset].isDigit())) { + number = getNumber(expr,*offset,&size); + result.append(number+","); + *offset += size; + } + // testing if it starts with a variable + else if (varMarkers.contains(expr[*offset])){ + number = getVariable(expr,*offset,&size); + result.append(number+","); + *offset += size; + } + + while (*offset= fctMarkers.size())) return -1; + return numFunc; +} + +bool ArithmeticEvaluator::checkAfterOp(const QString& _expression, int offset) { + int size; + if (offset+1 >= _expression.size()) return false; + + if (_expression[offset+1] == '(') return true; + else if (_expression[offset+1].isDigit()) return true; + else if (_expression[offset+1] == '-') { + if ((offset+2 < _expression.size()) && (_expression[offset+2].isDigit())) return true; + } + else if (varMarkers.contains(_expression[offset+1])) { + if ((offset+2 < _expression.size()) && (_expression[offset+2].isLetterOrNumber())) return true; + } + else if (getFunction(_expression, offset+1,&size) != -1) { + return true; + } + + return false; +} + +bool ArithmeticEvaluator::checkAfterPar(const QString& _expression, int offset) { + if (offset >= _expression.size()) return false; + // if ) is the last char of the expression : OK + if (offset == _expression.size()-1) return true; + + if (_expression[offset+1] == ')') return true; + else if (_expression[offset+1] == '+') return true; + else if (_expression[offset+1] == '-') return true; + else if (_expression[offset+1] == '*') return true; + else if (_expression[offset+1] == '/') return true; + + return false; +} diff --git a/ArithmeticEvaluator.h b/ArithmeticEvaluator.h new file mode 100644 index 0000000..f6d478b --- /dev/null +++ b/ArithmeticEvaluator.h @@ -0,0 +1,60 @@ +/*-==============================================================- + +file : ArithmeticEvaluator.h + +creation date : 19/05/2015 + +author : S. Domas (sdomas@univ-fcomte.fr) + +description : + +supp. infos : saved in UTF-8 [éè] + +-==============================================================-*/ +#ifndef __ARITHMETICEVALUATOR_H__ +#define __ARITHMETICEVALUATOR_H__ + +#include +#include + +#include + + +using namespace std; +using namespace Qt; + +class ArithmeticEvaluator { + +public: + + ArithmeticEvaluator(); + ArithmeticEvaluator(const QString& _expression) throw(int); + + void setExpression(const QString& _expression) throw(int); + inline void setVariablesValue(const QHash& _varValues) { varValues = _varValues; } + inline void setVariableValue(const QString& var, double value) { varValues.insert(var,value); } + inline void setVariableMarkers(const QString& _markers) { varMarkers = _markers; } + + void print(); + double evaluate() throw(int); + +protected: + QStringList expression; + QHash varValues; + QString varMarkers; // a sequence of symbols that are allowed to start a variable. $ is by default + QString opMarkers; // a sequence if symbols used as operators. +-*/ is the hard-coded default + QStringList fctMarkers; + + void convert(const QString& _expression) throw(int); + QString convertRecur(const QString& _expression, int* offset) throw(int); + QString getNumber(const QString& _expression, int offset, int *size); + QString getVariable(const QString& _expression, int offset, int *size); + int getFunction(const QString& _expression, int offset, int *size); + bool checkAfterOp(const QString& _expression, int offset); + bool checkAfterPar(const QString& _expression, int offset); + + double evalFunction(int indexFunc, double value); + +}; + +#endif //__ARITHMETICEVALUATOR_H__ diff --git a/BlockCategory.cpp b/BlockCategory.cpp new file mode 100644 index 0000000..0de4d86 --- /dev/null +++ b/BlockCategory.cpp @@ -0,0 +1,47 @@ +#include "BlockCategory.h" + +BlockCategory::BlockCategory(QString _name, int _id, BlockCategory* _parent) { + name = _name; + id = _id; + parent = _parent; +} + +void BlockCategory::addChild(BlockCategory* child) { + childs.append(child); +} + + + +BlockCategory* BlockCategory::getChild(QString name) { + QListIterator iter(childs); + BlockCategory* item = NULL; + while(iter.hasNext()) { + item = iter.next(); + if (item->name == name) return item; + } + return NULL; +} + +BlockCategory* BlockCategory::getChild(int index) { + if ((index >=0) && (index < childs.size()) ) { + return childs.at(index); + } + return NULL; +} + +QList BlockCategory::getAllChilds() +{ + return childs; +} + +ReferenceBlock *BlockCategory::getBlock(int index) { + if ((index >=0) && (index < blocks.size()) ) { + return blocks.at(index); + } + cout << "block null!" << endl; + return NULL; +} + +QDomElement BlockCategory::save(QDomDocument &doc) { +} + diff --git a/BlockCategory.h b/BlockCategory.h new file mode 100644 index 0000000..78f646e --- /dev/null +++ b/BlockCategory.h @@ -0,0 +1,50 @@ +#ifndef BLOCKCATEGORY_H +#define BLOCKCATEGORY_H + +#include + +#include +#include +#include "ReferenceBlock.h" +class ReferenceBlock; + +using namespace std; +using namespace Qt; + +class Block; + +class BlockCategory { + +public : + BlockCategory(QString _name, int _id, BlockCategory* _parent = NULL); + int id; + // getters + inline int getId() { return id; } + inline QString getName() { return name; } + inline BlockCategory* getParent() { return parent; } + inline QList getChilds() { return childs; } + BlockCategory* getChild(QString name); + BlockCategory* getChild(int index); + QList getAllChilds(); + inline QList getBlocks() { return blocks; } + ReferenceBlock *getBlock(int index); + + // setters + void addChild(BlockCategory* child); + inline void setParent(BlockCategory* _parent) { parent = _parent; } + + // I/O + QDomElement save(QDomDocument &doc); + + BlockCategory *getRoot(); + + //int id; + QList childs; + BlockCategory* parent; + QList blocks; +private: + QString name; + +}; + +#endif // BLOCKCATEGORY_H diff --git a/BlockImplementation.cpp b/BlockImplementation.cpp new file mode 100644 index 0000000..571b72b --- /dev/null +++ b/BlockImplementation.cpp @@ -0,0 +1,560 @@ +#include "BlockImplementation.h" + +#include "FunctionalBlock.h" +#include "ReferenceBlock.h" +#include "ReferenceInterface.h" +#include "FunctionalInterface.h" +#include "BlockParameter.h" + + +BlockImplementation::BlockImplementation(const QString& _xmlFile) { + xmlFile = _xmlFile; + + evaluator = new ArithmeticEvaluator; + evaluator->setVariableMarkers("@$"); +} + +BlockImplementation::BlockImplementation(const QString& _xmlFile, const QString &_referenceXml, const QString &_referenceMd5) { + xmlFile = _xmlFile; + referenceXml = _referenceXml; + referenceMd5 = _referenceMd5; +} + +void BlockImplementation::generateVHDL(FunctionalBlock* _block, const QString &path) throw(Exception) { + + block = _block; + + QFile implFile(xmlFile); + + // reading in into QDomDocument + QDomDocument document("implFile"); + + if (!implFile.open(QIODevice::ReadOnly)) { + throw(Exception(IMPLFILE_NOACCESS)); + } + if (!document.setContent(&implFile)) { + implFile.close(); + throw(Exception(IMPLFILE_NOACCESS)); + } + implFile.close(); + + bool genController = false; + QString coreFile = ""; + QString controllerFile = ""; + + if (reference->isWBConfigurable()) { + genController = true; + controllerFile = path; + controllerFile.append(block->getName()); + controllerFile.append("_ctrl.vhd"); + } + else { + controllerFile = "nofile.vhd"; + } + coreFile = path; + coreFile.append(block->getName()); + coreFile.append(".vhd"); + + QFile vhdlCore(coreFile); + QFile vhdlController(controllerFile); + + if (!vhdlCore.open(QIODevice::WriteOnly)) { + throw(Exception(VHDLFILE_NOACCESS)); + } + + if (genController) { + if (!vhdlController.open(QIODevice::WriteOnly)) { + throw(Exception(VHDLFILE_NOACCESS)); + } + } + QTextStream outCore(&vhdlCore); + QTextStream outController; + if (genController) { + outController.setDevice(&vhdlController); + } + + try { + + + //Get the root element + QDomElement impl = document.documentElement(); + QDomElement eltComments = impl.firstChildElement("comments"); + generateComments(eltComments, coreFile, outCore); + QDomElement eltLibs = eltComments.nextSiblingElement("libraries"); + generateLibraries(eltLibs, outCore); + generateEntity(outCore, genController); + QDomElement eltArch = eltLibs.nextSiblingElement("architecture"); + generateArchitecture(eltArch, outCore); + if (genController) { + generateController(outController); + } + } + catch(Exception err) { + throw(err); + } + + vhdlCore.close(); + vhdlController.close(); +} + +// This function generates the comments part of the VHDL document +void BlockImplementation::generateComments(QDomElement &elt, QString coreFile, QTextStream& out) throw(Exception) { + + for(int i = 0; i < 50; i++) { + out << "--"; + } + out << "\n--\n"; + QString fileName = coreFile; + out << "-- File : " << fileName << "\n"; + out << "--\n"; + QDomElement eltAuthor = elt.firstChildElement("author"); + QString firstName = eltAuthor.attribute("firstname",""); + QString lastName = eltAuthor.attribute("lastname",""); + QString mail = eltAuthor.attribute("mail",""); + out << "-- Author(s) : "<getName(); + //QList listParams = reference->getParameters(); + QList listInputs = reference->getInputs(); + QList listOutputs = reference->getOutputs(); + QList listBidirs = reference->getBidirs(); + QString typePort, namePort; + + out << "entity " << nameEnt << " is\n"; + + + /* TODO : rewrite the generation to take into acocunt the new object hierarchy */ + + // Generation of the generics + QList listGenerics = reference->getGenericParameters(); + if ((!listGenerics.isEmpty()) || (hasController)) { + out << " generic (" << endl; + if (hasController) { + out << " wb_data_width : integer = 16;" << endl; + out << " wb_addr_width : integer = 12"; + if (!listGenerics.isEmpty()) out << ";"; + out << endl; + } + for(i=0;itoVHDL(BlockParameter::Entity, 0); + } + out << " " << listGenerics.at(i)->toVHDL(BlockParameter::Entity,BlockParameter::NoComma); + + out << " );" << endl; + } + + out << " port (" << endl; + + // Generation of the clk & rst signals + out << " -- clk/rst" << endl; + for(int i = 0; i < listInputs.size(); i++) { + if(listInputs.at(i)->getPurpose() == AbstractInterface::Clock || listInputs.at(i)->getPurpose() == AbstractInterface::Reset) { + out << " " << listInputs.at(i)->getName() << " : in std_logic;" << endl; + } + } + + if (hasController) { + // Generation of the wishbone signals + out << " -- registers r/w via wishbone" << endl; + QList listWB = reference->getWishboneParameters(); + for(i=0;itoVHDL(BlockParameter::Entity, 0); + } + out << " " << listWB.at(i)->toVHDL(BlockParameter::Entity,BlockParameter::NoComma); + } + + + // Generation of the data signals + out << "-- data ports\n"; + for(int i = 0; i < listInputs.size(); i++) { + namePort = getIfaceUserName(reference->AbstractBlock::getIfaceFromName(listInputs.at(i)->getName())); + if(listInputs.at(i)->getWidth().compare("1")) + typePort = "std_logic"; + else + typePort = calculateWidth(listInputs.at(i)->getWidth()); + if(listInputs.at(i)->getPurpose() == 1) + out << namePort << " : in std_logic_vector(" << typePort << " -1 downto 0) ;\n"; + } + + for(int i = 0; i < listOutputs.size(); i++) { + namePort = getIfaceUserName(reference->AbstractBlock::getIfaceFromName(listOutputs.at(i)->getName())); + if(listOutputs.at(i)->getWidth().compare("1")) + typePort = "std_logic"; + else + typePort = calculateWidth(listOutputs.at(i)->getWidth()); + if(listOutputs.at(i)->getPurpose() == 1) + out << namePort << " : out std_logic_vector(" << typePort << " -1 downto 0) ;\n"; + } + + for(int i = 0; i < listBidirs.size(); i++) { + namePort = getIfaceUserName(reference->AbstractBlock::getIfaceFromName(listBidirs.at(i)->getName())); + if(listBidirs.at(i)->getWidth().compare(("1"))) + typePort = "std_logic"; + else + typePort = calculateWidth((listBidirs.at(i)->getWidth())); + if(listBidirs.at(i)->getPurpose() == 1) + out << namePort << " : inout std_logic_vector(" << typePort << " -1 downto 0) ;\n"; + } +} + +// This function generates the architecture part of the VHDL document +void BlockImplementation::generateArchitecture(QDomElement &elt, QTextStream& out) throw(Exception) { + + QString expr; + QDomElement eltArch = elt.nextSiblingElement("architecture"); + out << "architecture " << nameEnt <<"_1 of " << nameEnt << "is\n"; + QString implText = eltArch.text(); + QStringList listLine = implText.split("\n"); + for(int i =0; i < listLine.size(); i++) { + if(listLine.at(i).contains(QRegularExpression("@foreach{")) != -1) { + while(listLine.at(i).compare("@endforeach") != -1) { + expr = expr + listLine.at(i) + '\n'; + i++; + } + expr = expr + listLine.at(i); + out << evalComplex(expr, 1) << '\n'; + } + if(listLine.at(i).contains(QRegularExpression("@caseeach{")) != -1) { + while(listLine.at(i).compare("@endcaseeach") != -1) { + expr = expr + listLine.at(i) + '\n'; + i++; + } + expr = expr + listLine.at(i); + out << evalComplex(expr, 2) << '\n'; + } + + if(listLine.at(i).contains('@') == -1) + out << listLine.at(i) << "\n"; + else + out << eval(listLine.at(i), out) << "\n"; + } +} + +void BlockImplementation::generateController(QTextStream &out) throw(Exception) { +} + +QString BlockImplementation::eval(QString line, QTextStream& out) { + QString res, s, begLine, endLine, expr; + evaluator->setExpression(line); + QRegExp *rxString = new QRegExp("(.*)@{(.*)}(.*)"); + QRegExp *rxValue = new QRegExp("(.*)@val{(.*)}(.*)"); + QRegExp *rxExpr = new QRegExp(".*@eval{(.*)}.*"); + + int nbAt = line.count('@'); + while(nbAt != 0) { + for(int i = 0; i < line.size(); i++) { + if(rxString->indexIn(line)) { + begLine = rxString->cap(1); + s = rxString->cap(2); + endLine = rxString->cap(3); + res = begLine + evalString(s) + endLine + '\n'; + nbAt --; + } + } + for(int i = 0; i < line.size(); i++) { + if(rxValue->indexIn(line)) { + begLine = rxValue->cap(1); + s = rxValue->cap(2); + endLine = rxValue->cap(3); + res = begLine + evalValue(s) + endLine + '\n'; + nbAt --; + } + } + for(int i = 0; i < line.size(); i++) { + if(rxExpr->indexIn(line)) { + expr = rxExpr->cap(1); + if(expr.count('@') == 0) { + evaluator->setExpression(expr); + s = QString::number(evaluator->evaluate()); + } + res = begLine + s + endLine + '\n'; + nbAt --; + } + } + } + return res; +} + +QString BlockImplementation::evalComplex(QString line, int id) { + QString res, s, begLine, endLine, expr; + QRegExp *rxString = new QRegExp("(.*)@{(.*)}(.*)"); + QRegExp *rxValue = new QRegExp("(.*)@val{(.*)}(.*)"); + QRegExp *rxExpr = new QRegExp(".*@eval{(.*)}.*"); + QRegExp *rxFor = new QRegExp("@foreach{.*}(@{.*})(.*)@endforeach"); + QRegExp *rxCase = new QRegExp("@caseeach{.*,(.*),(.*)}(@{.*})(.*)@endcaseeach"); + QRegExp *rxCaseDown = new QRegExp("@#-:(.*)"); + QRegExp *rxCaseUp = new QRegExp("@#:(.*)"); + evaluator->setExpression(line); + + int nbAt = line.count('@') - 2; + while(nbAt != 0) { + for(int i = 0; i < line.size(); i++) { + if(rxString->indexIn(line)) { + begLine = rxString->cap(1); + s = rxString->cap(2); + endLine = rxString->cap(3); + if(evalStringComplex(s)->size() == 0) + line = begLine + evalString(s) + endLine; + nbAt --; + } + } + for(int i = 0; i < line.size(); i++) { + if(rxValue->indexIn(line)) { + begLine = rxValue->cap(1); + s = rxValue->cap(2); + endLine = rxValue->cap(3); + line = begLine + evalValue(s) + endLine; + nbAt --; + } + } + for(int i = 0; i < line.size(); i++) { + if(rxExpr->indexIn(line)) { + expr = rxExpr->cap(1); + if(expr.count('@') == 0) { + evaluator->setExpression(expr); + s = QString::number(evaluator->evaluate()); + } + res = begLine + s + endLine + '\n'; + nbAt --; + } + } + } + + if(id == 1) { + if(rxFor->indexIn(line)) { + QString intName, instruc; + intName = rxFor->cap(1); + instruc = rxFor->cap(2); + QList *intList = evalStringComplex(intName); + if(intList->size() != 0) { + for(int i = 0; i < intList->size(); i++) { + res = intList->at(i)->getName() + instruc + '\n'; + } + } + } + } + + else if(id == 2) { + if(rxCase->indexIn(line)) { + QString intName, sigName, cases, instruc; + int number; + sigName = rxCase->cap(1); + cases = rxCase->cap(2); + intName = rxCase->cap(3); + instruc = rxCase->cap(4); + QList *intList = evalStringComplex(intName); + int listSize = intList->count(); + res = "case " + sigName + " is\n"; + if(rxCaseUp->indexIn(cases)) { + number = rxCaseUp->cap(1).toInt(); + for(int j = number; j < listSize; j++) { + if(listSize > 0) { + for(int i = 0; i < listSize; i++) { + res += "\twhen " + QString::number(number) + " " + intList->at(i)->getName() + instruc + "\n"; + } + } + else + res += "\twhen " + number + ' ' + intName + instruc + "\n"; + number++; + } + } + if(rxCaseDown->indexIn(cases)) { + number = rxCaseDown->cap(1).toInt(); + for(int j = number; j < listSize; j++) { + if(listSize > 0) { + for(int i = 0; i < listSize; i++) { + res += "\twhen " + QString::number(number) + " " + intList->at(i)->getName() + instruc + "\n"; + } + } + else + res += "\twhen " + number + ' ' + intName + instruc + "\n"; + number--; + } + res += "end case ;\n"; + } + } + } + return res; +} + +QString BlockImplementation::evalString(QString s) { + + QString name = getIfaceUserName(block->AbstractBlock::getIfaceFromName(s)); + return name; +} + +QList* BlockImplementation::evalStringComplex(QString s) { + + int j = 0; + QList *listInterfaces = new QList(); + AbstractInterface *inter = block->AbstractBlock::getIfaceFromName(s); + QList listIntBlock = block->getInterfaces(); + for(int i = 0; i < listIntBlock.size(); i++) { + if(inter->getName().compare(listIntBlock.at(i)->getName()) < -1) { + listInterfaces->insert(j, inter); + j ++; + } + } + return listInterfaces; +} + +QString BlockImplementation::evalValue(QString s) { + + QString val = ""; + if(paramMap.contains(s)) + val = paramMap.value(s); + return val; +} + +QString BlockImplementation::getIfaceUserName(AbstractInterface* refIface) { + + if (! refIface->isReferenceInterface()) return ""; + + AbstractInterface* funcIface = NULL; + + if (refIface->getDirection() == AbstractInterface::Input) { + foreach(AbstractInterface* iface, block->getInputs()) { + FunctionalInterface* fi = (FunctionalInterface*)iface; + if (fi->getReference() == refIface) { + funcIface = iface; + break; + } + } + } + else if (refIface->getDirection() == AbstractInterface::Output) { + foreach(AbstractInterface* iface, block->getOutputs()) { + FunctionalInterface* fi = (FunctionalInterface*)iface; + if (fi->getReference() == refIface) { + funcIface = iface; + break; + } + } + } + else if (refIface->getDirection() == AbstractInterface::InOut) { + foreach(AbstractInterface* iface, block->getBidirs()) { + FunctionalInterface* fi = (FunctionalInterface*)iface; + if (fi->getReference() == refIface) { + funcIface = iface; + break; + } + } + } + if (funcIface == NULL) return ""; + + return funcIface->getName(); +} + +QDataStream& operator<<(QDataStream &out, const BlockImplementation &impl) { + + out.setVersion(QDataStream::Qt_5_0); + + QByteArray blockData; + QDataStream toWrite(&blockData, QIODevice::WriteOnly); + + toWrite << impl.xmlFile; + toWrite << impl.referenceXml; + toWrite << impl.referenceMd5; + + out << blockData; + + return out; +} + +QDataStream& operator>>(QDataStream &in, BlockImplementation &impl) { + + quint32 blockSize; + + in.setVersion(QDataStream::Qt_5_0); + + in >> blockSize; + + in >> impl.xmlFile; + in >> impl.referenceXml; + in >> impl.referenceMd5; + + return in; +} + +QString BlockImplementation::calculateWidth(QString s){ + QRegExp *rxWidth = new QRegExp("$*([a-zA-Z0-9_-]*)"); + QStringList matchList = s.split(" "); + int pos = 0; + QString res, line; + QList listParams = reference->getParameters(); + + while ((pos = rxWidth->indexIn(s, pos)) != -1) { + matchList << rxWidth->cap(1); + pos += rxWidth->matchedLength(); + } + + for (int i = 0; i < matchList.size(); i++) { + QString match = matchList.at(i); + if(rxWidth->indexIn(match)) { + for(int j = 0; j < listParams.size(); j++) { + if(match.compare(listParams.at(j)->getName())) { + BlockParameter *param = listParams.at(i); + if(param->getContext() == "generic") { + match = match.remove('$'); + } + else { + match = param->getValue().toString(); + } + } + } + } + } + line = matchList.join(' '); + evaluator->setExpression(line); + res = evaluator->evaluate(); + return res; +} + diff --git a/BlockImplementation.h b/BlockImplementation.h new file mode 100644 index 0000000..df0e499 --- /dev/null +++ b/BlockImplementation.h @@ -0,0 +1,68 @@ +#ifndef __BLOCKIMPLEMENTATION_H__ +#define __BLOCKIMPLEMENTATION_H__ + +#include +#include + +#include +#include + +class ReferenceBlock; +class FunctionalBlock; +class AbstractInterface; + +#include "ArithmeticEvaluator.h" +class ArithmeticEvaluator; + +#include "Exception.h" +class Exception; + + +using namespace std; +using namespace Qt; + +class BlockImplementation { + +public: + + BlockImplementation(const QString& _xmlFile); + BlockImplementation(const QString& _xmlFile, const QString& _referenceXml, const QString& _referenceMd5); + + inline QString getXmlFile() { return xmlFile; } + inline QString getReferenceXml() { return referenceXml; } + inline QString getReferenceMd5() { return referenceMd5; } + QString eval(QString line, QTextStream& out); + QString evalComplex(QString line, int num); + QString evalString(QString s); + QList* evalStringComplex(QString s); + QString evalValue(QString s); + QString calculateWidth(QString s); + + inline void setReference(ReferenceBlock* _reference) { reference = _reference; } + + void generateVHDL(FunctionalBlock* _block, const QString& path) throw(Exception); // main entry to generate the VHDL code + +private: + QString xmlFile; + QString referenceXml; + QString referenceMd5; + QString nameEnt, line; + QMap paramMap; + ArithmeticEvaluator* evaluator; + ReferenceBlock* reference; + FunctionalBlock* block; // the current functional block for which this implementation is used. + + void generateComments(QDomElement &elt,QString coreFile, QTextStream& out) throw(Exception); // generates comments from element + void generateLibraries(QDomElement &elt, QTextStream& out) throw(Exception); // generates libraries from element + void generateEntity(QTextStream& out, bool hasController=false) throw(Exception); // generate the entity using reference + void generateArchitecture(QDomElement &elt, QTextStream& out) throw(Exception); // generate the architecture using element + void generateController(QTextStream& out) throw(Exception); // generate the wishbone controller of the block + + QString getIfaceUserName(AbstractInterface* refIface); // get the name of an interface given by the user, from the reference interface + + friend QDataStream &operator<<(QDataStream &out, const BlockImplementation &impl); + friend QDataStream &operator>>(QDataStream &in, BlockImplementation &impl); +}; + +#endif // __BLOCKIMPLEMENTATION_H__ + diff --git a/BlockLibraryTree.cpp b/BlockLibraryTree.cpp new file mode 100644 index 0000000..33980f9 --- /dev/null +++ b/BlockLibraryTree.cpp @@ -0,0 +1,137 @@ +#include "BlockLibraryTree.h" + +BlockLibraryTree::BlockLibraryTree() { + tabCategories = NULL; + tabIdParent = NULL; + nbCategories = 0; +} + +BlockLibraryTree::~BlockLibraryTree() { + clear(); +} + +void BlockLibraryTree::clear() { + + for(int i=0;iblocks.clear(); + } +} + +void BlockLibraryTree::addItem(QXmlAttributes &attributes) +{ + nbCategories++; + if(tabCategories == NULL){ + tabCategories = new BlockCategory* [1]; + tabIdParent = new int[1]; + } + else{ + BlockCategory** tmpTabCat = new BlockCategory* [nbCategories]; + int* tmpTabParent = new int[nbCategories]; + for(int i=0; i< nbCategories; i++){ + tmpTabCat[i] = tabCategories[i]; + tmpTabParent[i] = tabIdParent[i]; + } + tabCategories = tmpTabCat; + tabIdParent = tmpTabParent; + } + + QString name = attributes.value(0); + int id = attributes.value(1).toInt(); + int idParent = attributes.value(2).toInt(); + BlockCategory* cat = new BlockCategory(name,id); + tabCategories[id] = cat; + tabIdParent[id] = idParent; +} + +bool BlockLibraryTree::initChildParent() +{ + // initializing parent/childs + for(int i=0;i= nbCategories) return false; + tabCategories[i]->setParent(tabCategories[tabIdParent[i]]); + tabCategories[tabIdParent[i]]->addChild(tabCategories[i]); + } + } + return true; +} + + +QDomElement BlockLibraryTree::save(QDomDocument &doc) { + +} + +/* NOTE : load() is the only way to initialize the tree. + It is done at the begining of the application, while reading + the configuration file. + elt MUST be the DOM element that corresponds to the tag + */ +void BlockLibraryTree::load(QDomElement &elt) throw(Exception) { + + if (elt.tagName() != "categories") throw(Exception(CONFIGFILE_CORRUPTED)); + + QString nbStr = elt.attribute("nb","none"); + bool ok; + int nb = nbStr.toInt(&ok); + QDomNodeList list = elt.elementsByTagName("category"); + nbCategories = list.size(); + if (nb != nbCategories) throw(Exception(CONFIGFILE_CORRUPTED)); + QString name; + int id; + QString idStr; + int idParent; + QString idParentStr; + + tabCategories = new BlockCategory* [nbCategories]; + tabIdParent = new int[nbCategories]; + BlockCategory* cat = NULL; + + // creating all BlockCategory objects + for(int i=0;i= nbCategories)) throw(Exception(CONFIGFILE_CORRUPTED)); + idParent = idParentStr.toInt(&ok); + if ((!ok)|| (idParent < -1) || (idParent >= nbCategories)) throw(Exception(CONFIGFILE_CORRUPTED)); + cat = new BlockCategory(name,id); + tabCategories[id] = cat; + tabIdParent[id] = idParent; + } + + ok = initChildParent(); + delete [] tabIdParent; + if (!ok) throw(Exception(CONFIGFILE_CORRUPTED)); +} + +BlockCategory* BlockLibraryTree::searchCategory(int id) { + + if (tabCategories != NULL) { + if ((id>=0) && (id < nbCategories)) { + return tabCategories[id]; + } + } + + return NULL; +} + +BlockCategory *BlockLibraryTree::getRoot() { + if (tabCategories != NULL) { + if (nbCategories > 0) { + return tabCategories[0]; + } + } + return NULL; +} diff --git a/BlockLibraryTree.h b/BlockLibraryTree.h new file mode 100644 index 0000000..f8fa60c --- /dev/null +++ b/BlockLibraryTree.h @@ -0,0 +1,50 @@ +#ifndef __BLOCKLIBRARYTREE_H__ +#define __BLOCKLIBRARYTREE_H__ + +#include +#include +#include +#include + +#include +#include + +#include + +#include "BlockCategory.h" +#include "Exception.h" + +using namespace std; +using namespace Qt; +class BlockCategory; +class BlockLibraryTree { + +public : + BlockLibraryTree(); + ~BlockLibraryTree(); + + void clear(); // free thewhole tree + void clearBlocks(); // just remove the blocks from the BlockCateogry lists + void addItem(QXmlAttributes &attributes); + bool initChildParent(); + + BlockCategory *getRoot(); + BlockCategory *searchCategory(int id); + + QDomElement save(QDomDocument &doc); + void load(QDomElement &elt) throw(Exception); +private: + /* NOTE : + This class builds a tree of BlockCategory, but it also stores all BlockCategory object + in an array so that access via an id is direct. + + The root of the tree is in fact tabCategories[0] + */ + BlockCategory** tabCategories; + int* tabIdParent; + int nbCategories; + +}; + + +#endif // BLOCKLIBRARYTREE_H diff --git a/BlockLibraryWidget.cpp b/BlockLibraryWidget.cpp new file mode 100644 index 0000000..0aa9ed1 --- /dev/null +++ b/BlockLibraryWidget.cpp @@ -0,0 +1,107 @@ +#include "BlockLibraryWidget.h" +#include "BlockLibraryTree.h" + +BlockLibraryWidget::BlockLibraryWidget(Dispatcher* _dispatcher, + Parameters* _params, + QWidget *parent) : QWidget(parent) { + + + dispatcher = _dispatcher; + params = _params; + + // creating the widget : tree, buttons, ... + layout = new QBoxLayout(QBoxLayout::TopToBottom, this); + tree = new QTreeWidget(this); + buttonAdd = new QPushButton("add", this); + buttonAdd->setEnabled(false); + + connect(tree, SIGNAL(itemClicked(QTreeWidgetItem*,int)), this, SLOT(clicked())); + connect(tree, SIGNAL(itemDoubleClicked(QTreeWidgetItem*,int)), this, SLOT(doubleClicked())); + connect(buttonAdd, SIGNAL(clicked()), this, SLOT(addClicked())); + + BlockCategory* cat = params->categoryTree->searchCategory(0); + + QTreeWidgetItem* item = tree->invisibleRootItem(); + tree->setHeaderLabel("Blocks list"); + + + addChild(cat,item); + + layout->addWidget(tree); + layout->addWidget(buttonAdd); + this->setLayout(layout); + + this->setFixedSize(300,230); +} + + +BlockLibraryWidget::~BlockLibraryWidget() { +} + +void BlockLibraryWidget::addChild(BlockCategory *catParent,QTreeWidgetItem* itemParent) { + + QTreeWidgetItem* newItemCat = NULL; + QTreeWidgetItem* newItemBlock = NULL; + + QList childs = catParent->getAllChilds(); + foreach(BlockCategory* cat, childs){ + newItemCat = new QTreeWidgetItem(itemParent); + newItemCat->setData(0,Qt::DisplayRole, cat->getName()); + QList list = cat->getBlocks(); + for(int i=0; isetData(0,Qt::DisplayRole, list.at(i)->getName()); + newItemBlock->setData(1,Qt::DisplayRole, cat->getId()); + newItemBlock->setData(2,Qt::DisplayRole, i); + newItemBlock->setIcon(0,QIcon("icons/window_new.png")); + } + addChild(cat,newItemCat); + } + /* TO DO : + - getting the childs of catParent + - for each BlockCategory cat of that list : + - create an new TreeWidgetItem (newImteCat), with itemParent as a parent + - set the first column of that item with the categry name + - get the list of the blocks that are associated with cat + - for i = 0 to that list size. + - create a new TreeWidgetItem (newItemBlock), with newItemCat as a parent + - set the first column of that item with the block name + - set the second column of that item with the newItemCat id + - set the third column of that item with the value of i + - endfor + - call again addChild with cat and newImteCat as parameters + - end for + */ +} + + +void BlockLibraryWidget::addClicked() { + + QTreeWidgetItem *item = tree->selectedItems().at(0); + if(item->data(1,Qt::DisplayRole).isValid() && item->data(2,Qt::DisplayRole).isValid()){ + int idParent = item->data(1,Qt::DisplayRole).toInt(); + int idBlock = item->data(2,Qt::DisplayRole).toInt(); + dispatcher->addBlock(idParent, idBlock); + } + + // only take the first selected + // retrieve id of category and id of block + // calling dispatcher addBlock() method. +} + + +void BlockLibraryWidget::clicked() +{ + if(tree->selectedItems().length() > 0){ + QTreeWidgetItem *item = tree->selectedItems().at(0); + if(item->data(1,Qt::DisplayRole).isValid()) + buttonAdd->setEnabled(true); + else + buttonAdd->setEnabled(false); + } +} + +void BlockLibraryWidget::doubleClicked() +{ + addClicked(); +} diff --git a/BlockLibraryWidget.h b/BlockLibraryWidget.h new file mode 100644 index 0000000..f07be4d --- /dev/null +++ b/BlockLibraryWidget.h @@ -0,0 +1,45 @@ +#ifndef __BLOCKLIBRARYWIDGET_H__ +#define __BLOCKLIBRARYWIDGET_H__ + +#include + +#include +#include + +#include "Dispatcher.h" +class Dispatcher; +#include "Parameters.h" +class Parameters; +#include "BlockCategory.h" +class BlockCategory; + +using namespace std; +using namespace Qt; + +class BlockLibraryWidget : public QWidget { + Q_OBJECT + +public: + explicit BlockLibraryWidget(Dispatcher* _dispatcher, Parameters* _params, QWidget *parent = 0); + ~BlockLibraryWidget(); + +private slots: + void addClicked(); + void clicked(); + void doubleClicked(); + + +private: + Parameters* params; + Dispatcher* dispatcher; + QTreeWidget* tree; + QPushButton* buttonAdd; + QBoxLayout *layout; + // other attributes + + void addChild(BlockCategory *catParent, QTreeWidgetItem* itemParent); + void addButtons(); + +}; + +#endif // __BLOCKLIBRARYWIDGET_H__ diff --git a/BlockParameter.cpp b/BlockParameter.cpp new file mode 100644 index 0000000..f8dfcd7 --- /dev/null +++ b/BlockParameter.cpp @@ -0,0 +1,121 @@ +#include "BlockParameter.h" + +BlockParameter::BlockParameter() { + owner = NULL; + name = ""; + type = BlockParameter::String; + defaultValue = QVariant(); +} + +BlockParameter::BlockParameter(AbstractBlock* _owner, const QString &_name, const QString &_type, const QString &_value) { + owner = _owner; + name =_name; + type = typeFromString(_type); + defaultValue = QVariant(_value); +} + +BlockParameter::~BlockParameter(){ + +} + +QVariant BlockParameter::getValue() { + return defaultValue; +} + +bool BlockParameter::isUserParameter() { + return false; + +} + +bool BlockParameter::isGenericParameter() { + return false; +} + +bool BlockParameter::isWishboneParameter() { + return false; +} + +bool BlockParameter::isPortParameter() { + return false; +} + +void BlockParameter::setValue(const QString& _value) { + defaultValue = QVariant(_value); +} + +bool BlockParameter::isValueSet() { + if (defaultValue.isNull()) return false; + return true; +} + +QString BlockParameter::toVHDL(int context, int flags) { + + QString ret=""; + return ret; +} + +QString BlockParameter::getTypeString() { + if (type == BlockParameter::Bit) { + return "bit"; + } + else if (type == BlockParameter::BitVector) { + return "bit_vector"; + } + else if (type == BlockParameter::Boolean) { + return "boolean"; + } + else if (type == BlockParameter::Integer) { + return "integer"; + } + else if (type == BlockParameter::Natural) { + return "natural"; + } + else if (type == BlockParameter::Positive) { + return "positive"; + } + else if (type == BlockParameter::Real) { + return "real"; + } + else if (type == BlockParameter::Character) { + return "character"; + } + else if (type == BlockParameter::String) { + return "string"; + } + else if (type == BlockParameter::Time) { + return "time"; + } + return "undefined"; +} + +int BlockParameter::typeFromString(const QString &_type) { + int ret = BlockParameter::Expression; + + if (_type == "string") { + ret = BlockParameter::String; + } + else if (_type == "expression") { + ret = BlockParameter::Expression; + } + else if (_type == "boolean") { + ret = BlockParameter::Boolean; + } + else if (_type == "integer") { + ret = BlockParameter::Integer; + } + else if (_type == "natural") { + ret = BlockParameter::Natural; + } + else if (_type == "positive") { + ret = BlockParameter::Positive; + } + else if (_type == "real") { + ret = BlockParameter::Real; + } + else if (_type == "time") { + ret = BlockParameter::Time; + } + return ret; +} + + diff --git a/BlockParameter.h b/BlockParameter.h new file mode 100644 index 0000000..e0c4af2 --- /dev/null +++ b/BlockParameter.h @@ -0,0 +1,68 @@ +#ifndef __BLOCKPARAMETER_H__ +#define __BLOCKPARAMETER_H__ + +#include +#include + +#include + +#include "AbstractBlock.h" +class AbstractBlock; + +using namespace std; +using namespace Qt; + + +class BlockParameter { + +public : + + enum ParamType { Expression = 1, Character, String, Bit, BitVector, Boolean, Integer, Natural, Positive, Real, Time}; + // a bit ugly to put that here but more practical for using them + enum ParamWBAccess { Read = 1, Write = 2}; + enum ParamWBDuration { Permanent = 1, Trigger = 2 }; + enum ParamVHDLContext { Entity = 1, Component = 2, Architecture = 3 }; // NB : 3 is when creating an instance of the block that owns this iface + enum ParamVHDLFlags { NoComma = 1 }; + + BlockParameter(); + BlockParameter(AbstractBlock* _owner, const QString& _name , const QString& _type, const QString& _value); + + virtual ~BlockParameter(); + + // getters + inline AbstractBlock* getOwner() { return owner; } + inline QString getName() { return name; } + inline int getType() { return type; } + QString getTypeString(); + virtual QVariant getValue(); // may be overriden + virtual QString getContext() = 0; + + // setters + inline void setOwner(AbstractBlock* _owner) { owner = _owner; } + inline void setName(const QString& _name) { name = _name; } + inline void setType(int _type) { type = _type; } + virtual void setValue(const QString& _value); + + // testers + virtual bool isValueSet(); // may be overridden for User and Generic parameters + virtual bool isUserParameter(); + virtual bool isGenericParameter(); + virtual bool isWishboneParameter(); + virtual bool isPortParameter(); + + // others + virtual BlockParameter* clone() = 0; + virtual QString toVHDL(int context, int flags); + int typeFromString(const QString& _type); + +protected: + + AbstractBlock* owner; + QString name; + int type; + QVariant defaultValue; // the value set during construction + +}; + +#endif // __BLOCKPARAMETER_H__ + diff --git a/BlockParameterGeneric.cpp b/BlockParameterGeneric.cpp new file mode 100644 index 0000000..5b15eb9 --- /dev/null +++ b/BlockParameterGeneric.cpp @@ -0,0 +1,97 @@ +#include "BlockParameterGeneric.h" +#include "GroupBlock.h" +#include "FunctionalBlock.h" + +BlockParameterGeneric::BlockParameterGeneric() : BlockParameter() { + userValue = defaultValue; +} + +BlockParameterGeneric::BlockParameterGeneric(AbstractBlock* _owner, const QString &_name, const QString &_type, const QString &_value) : BlockParameter(_owner, _name, _type, _value) { + userValue = defaultValue; +} + +QVariant BlockParameterGeneric::getValue() { + if (isValueSet()) { + return userValue; + } + return defaultValue; +} + +bool BlockParameterGeneric::isGenericParameter() { + return true; +} + +void BlockParameterGeneric::setValue(const QString& _value) { + userValue = QVariant(_value); +} + + +bool BlockParameterGeneric::isValueSet() { + if (userValue.isNull()) return false; + return true; +} + +bool BlockParameterGeneric::isDefaultValue() { + if (userValue == defaultValue) return true; + return false; +} + +BlockParameter* BlockParameterGeneric::clone() { + + BlockParameter* block = new BlockParameterGeneric(owner,name,getTypeString(),defaultValue.toString()); + return block; +} + +QString BlockParameterGeneric::toVHDL(int context, int flags) { + + QString ret=""; + + + if ((context == BlockParameter::Entity) || (context == BlockParameter::Component)) { + + QString formatValue = "%1 : %2 := %3"; + QString formatNoValue = "%1 : %2"; + if ((flags & BlockParameter::NoComma) == 0) { + formatValue.append(";"); + formatNoValue.append(";"); + } + + if (!userValue.isNull()) { + ret = formatValue.arg(name).arg(type).arg(userValue.toString()); + } + else if (!defaultValue.isNull()) { + ret = formatValue.arg(name).arg(type).arg(defaultValue.toString()); + } + else { + ret = formatNoValue.arg(name).arg(type); + } + } + else if (context == BlockParameter::Architecture) { + QString format = "%1 => %2"; + if ((flags & BlockParameter::NoComma) == 0) { + format.append(";"); + } + AbstractBlock* parent = owner->getParent(); + BlockParameter* p = parent->getParameterFromName(name); + if (p != NULL) { + /* the parent group has a generic parameter with the same + name + */ + ret = format.arg(name).arg(name); + } + else { + if (!userValue.isNull()) { + ret = format.arg(name).arg(userValue.toString()); + } + else if (!defaultValue.isNull()) { + ret = format.arg(name).arg(defaultValue.toString()); + } + else { + // abnormal case + ret = format.arg(name).arg("INVALID_VALUE"); + } + } + } + return ret; +} + diff --git a/BlockParameterGeneric.h b/BlockParameterGeneric.h new file mode 100644 index 0000000..9fb81a6 --- /dev/null +++ b/BlockParameterGeneric.h @@ -0,0 +1,46 @@ +#ifndef __BLOCKPARAMETERGENERIC_H__ +#define __BLOCKPARAMETERGENERIC_H__ + +#include +#include + +#include + +#include "BlockParameter.h" +class BlockParameter; + +using namespace std; +using namespace Qt; + + +class BlockParameterGeneric : public BlockParameter { + +public : + + BlockParameterGeneric(); + BlockParameterGeneric(AbstractBlock* _owner, const QString& _name, const QString& _type, const QString& _value); + + // getters + QVariant getValue(); + inline QString getContext() { return "generic";} + // setters + void setValue(const QString& _value); + inline void resetToDefaultValue() { userValue = defaultValue; } + + // testers + bool isValueSet(); + bool isDefaultValue(); + bool isGenericParameter(); + + // others + BlockParameter* clone(); + QString toVHDL(int context, int flags); + +private: + + QVariant userValue; + +}; + +#endif // __BLOCKPARAMETERGENERIC_H__ + diff --git a/BlockParameterPort.cpp b/BlockParameterPort.cpp new file mode 100644 index 0000000..c33724b --- /dev/null +++ b/BlockParameterPort.cpp @@ -0,0 +1,58 @@ +#include "BlockParameterPort.h" +#include "ArithmeticEvaluator.h" +#include "FunctionalBlock.h" +#include "FunctionalInterface.h" + +BlockParameterPort::BlockParameterPort() : BlockParameter() { + ifaceName = ""; +} + +BlockParameterPort::BlockParameterPort(AbstractBlock* _owner, const QString &_name, const QString &_value, const QString &_ifaceName) : BlockParameter(_owner, _name, "expression", _value) { + ifaceName = _ifaceName; +} + +bool BlockParameterPort::isPortParameter() { + return true; +} + +BlockParameter* BlockParameterPort::clone() { + BlockParameter* block = new BlockParameterPort(owner,name,defaultValue.toString(),ifaceName); + return block; +} + +QString BlockParameterPort::toVHDL(int context, int flags) { + QString expr=""; + QString ret=""; + ArithmeticEvaluator evaluator; + + if (!defaultValue.isNull()) { + expr = defaultValue.toString(); + try { + evaluator.setVariableMarkers("$"); + evaluator.setExpression(expr); + + double ifaceNb = 0.0; + double ifaceWidth = 0.0; + FunctionalInterface* iface = (FunctionalInterface*)(owner->getIfaceFromName(ifaceName)); + if (iface == NULL) return "INVALID_INTERFACE_NAME"; + + // must get the number of instance of + ifaceNb = iface->getInterfaceMultiplicity(); + + int result = 0; + evaluator.setVariableValue("$if_width",ifaceWidth); + evaluator.setVariableValue("$if_nb",ifaceNb); + result = (int)(evaluator.evaluate()); + ret.setNum(result); + } + catch(int e) { + cerr << "invalid expression in port parameter " << qPrintable(name) << " at character " << e << endl; + } + } + + return ret; +} + +void BlockParameterPort::setIfaceName(const QString& _ifaceName) { + ifaceName = _ifaceName; +} diff --git a/BlockParameterPort.h b/BlockParameterPort.h new file mode 100644 index 0000000..3050a1b --- /dev/null +++ b/BlockParameterPort.h @@ -0,0 +1,42 @@ +#ifndef __BLOCKPARAMETERPORT_H__ +#define __BLOCKPARAMETERPORT_H__ + +#include +#include + +#include + +#include "BlockParameter.h" +class BlockParameter; + +using namespace std; +using namespace Qt; + +class BlockParameterPort : public BlockParameter { + +public : + + BlockParameterPort(); + BlockParameterPort(AbstractBlock* _owner, const QString& _name, const QString& _value, const QString& _ifaceName); + + // getters + inline QString getIfaceName() { return ifaceName; } + inline QString getContext() { return "port";} + // setters + void setIfaceName(const QString& _ifaceName); + + // testers + bool isPortParameter(); + + // others + BlockParameter* clone(); + QString toVHDL(int context, int flags); + +private: + + QString ifaceName; + +}; + +#endif // __BLOCKPARAMETERPORT_H__ + diff --git a/BlockParameterUser.cpp b/BlockParameterUser.cpp new file mode 100644 index 0000000..d0d870f --- /dev/null +++ b/BlockParameterUser.cpp @@ -0,0 +1,56 @@ +#include "BlockParameterUser.h" + +BlockParameterUser::BlockParameterUser() : BlockParameter() { + userValue = defaultValue; +} + +BlockParameterUser::BlockParameterUser(AbstractBlock* _owner, const QString &_name, const QString &_value) : BlockParameter(_owner, _name, "string", _value) { + userValue = defaultValue; +} + +QVariant BlockParameterUser::getValue() { + if (isValueSet()) { + return userValue; + } + return defaultValue; +} + +bool BlockParameterUser::isUserParameter() { + return true; +} + +bool BlockParameterUser::isValueSet() { + if (userValue.isNull()) return false; + return true; +} + +bool BlockParameterUser::isDefaultValue() { + if (userValue == defaultValue) return true; + return false; +} + +BlockParameter* BlockParameterUser::clone() { + BlockParameter* block = new BlockParameterUser(owner,name,defaultValue.toString()); + return block; +} + +QString BlockParameterUser::toVHDL(int context, int flags) { + + // NB : context and flags are purely ignored + QString ret=""; + if (!userValue.isNull()) { + ret = userValue.toString(); + } + if (!defaultValue.isNull()) { + ret = defaultValue.toString(); + } + else { + ret = ""; + } + return ret; +} + +void BlockParameterUser::setValue(const QString& _value) { + userValue = QVariant(_value); +} + diff --git a/BlockParameterUser.h b/BlockParameterUser.h new file mode 100644 index 0000000..3da12a3 --- /dev/null +++ b/BlockParameterUser.h @@ -0,0 +1,57 @@ +#ifndef __BLOCKPARAMETERUSER_H__ +#define __BLOCKPARAMETERUSER_H__ + +#include +#include + +#include + +#include "BlockParameter.h" +class BlockParameter; + +using namespace std; +using namespace Qt; + +/* NOTES : + + A BlockParameterUser represents string that will be put in the generated VHDL code + each time a @val{param_name} is encountered. The default value of the string is given + at construction. If it is empty, user will have to provide a value before VHDL generation. + If it's not empty, user can still change this value before generation. + Since this string can be almost everything, it may lead to incorrect VHDL code. + But no validity check is done ! + + The type of such a parameter is always "string". + + */ +class BlockParameterUser : public BlockParameter { + +public : + + BlockParameterUser(); + BlockParameterUser(AbstractBlock* _owner, const QString& _name, const QString& _value); + + // getters + QVariant getValue(); + inline QString getContext() { return "user";} + // setters + void setValue(const QString& _value); + inline void resetToDefaultValue() { userValue = defaultValue; } + + // testers + bool isValueSet(); + bool isDefaultValue(); + bool isUserParameter(); + + // others + BlockParameter* clone(); + QString toVHDL(int context, int flags); + +private: + + QVariant userValue; + +}; + +#endif // __BLOCKPARAMETERUSER_H__ + diff --git a/BlockParameterWishbone.cpp b/BlockParameterWishbone.cpp new file mode 100644 index 0000000..4d13338 --- /dev/null +++ b/BlockParameterWishbone.cpp @@ -0,0 +1,90 @@ +#include "BlockParameterWishbone.h" + +BlockParameterWishbone::BlockParameterWishbone() : BlockParameter() { + userValue = defaultValue; + width = "0"; + wbAccess = BlockParameter::Read; + wbValue = "0"; + wbDuration = BlockParameter::Permanent; +} + +BlockParameterWishbone::BlockParameterWishbone(AbstractBlock* _owner, const QString& _name , const QString& _type, const QString& _width, const QString& _value, int _wbAccess, QString _wbValue, int _wbDuration) : BlockParameter (_owner, _name, _type, _value) { + userValue = _value; + width = _width; + wbAccess = _wbAccess; + wbValue = _wbValue; + wbDuration = _wbDuration; +} + +QVariant BlockParameterWishbone::getValue() { + if (isValueSet()) { + return userValue; + } + return defaultValue; +} + +bool BlockParameterWishbone::isWishboneParameter() { + return true; +} + +void BlockParameterWishbone::setValue(const QString& _value) { + userValue = QVariant(_value); +} + +bool BlockParameterWishbone::isValueSet() { + if (userValue.isNull()) return false; + return true; +} + +bool BlockParameterWishbone::isDefaultValue() { + if (userValue == defaultValue) return true; + return false; +} + +BlockParameter* BlockParameterWishbone::clone() { + BlockParameter* block = new BlockParameterWishbone(owner,name,getTypeString(),width,defaultValue.toString(),wbAccess,wbValue,wbDuration); + return block; +} + +QString BlockParameterWishbone::toVHDL(int context, int flags) { + + QString ret=""; + bool ok; + if ((context == BlockParameter::Entity) || (context == BlockParameter::Component)) { + + QString formatBool = "%1 : %2 std_logic"; + QString formatVector = "%1 : %2 std_logic_vector(%3 downto %4)"; + if ((flags & BlockParameter::NoComma) == 0) { + formatBool.append(";"); + formatVector.append(";"); + } + QString orientation=""; + if (wbAccess == Read) { + orientation = "out"; + } + else { + orientation = "in"; + } + if (type == Boolean) { + ret = formatVector.arg(name).arg(orientation); + } + else if (type == Natural) { + int w = width.toInt(&ok); + if (!ok) { + ret = formatVector.arg(name).arg(orientation).arg("INVALID SIZE").arg("0"); + } + else { + w -= 1; + ret = formatVector.arg(name).arg(orientation).arg(w).arg("0"); + } + } + else if (type == Expression) { + QString w = width.remove('$'); + w.append("-1"); + ret = formatVector.arg(name).arg(orientation).arg(w).arg("0"); + } + } + return ret; +} + + diff --git a/BlockParameterWishbone.h b/BlockParameterWishbone.h new file mode 100644 index 0000000..37be6c0 --- /dev/null +++ b/BlockParameterWishbone.h @@ -0,0 +1,58 @@ +#ifndef __BLOCKPARAMETERWISHBONE_H__ +#define __BLOCKPARAMETERWISHBONE_H__ + +#include +#include + +#include + +#include "BlockParameter.h" +class BlockParameter; + +using namespace std; +using namespace Qt; + +class BlockParameterWishbone : public BlockParameter { + +public : + + BlockParameterWishbone(); + BlockParameterWishbone(AbstractBlock* _owner, const QString& _name , const QString& _type, const QString& _width, const QString& _value, int _wbAccess = BlockParameter::Read, QString _wbValue = QString(), int _wbDuration = BlockParameter::Permanent); + + // getters + QVariant getValue(); + inline QString getContext() { return "wb";} + inline QString getWidth() { return width; } + inline int getWBAccess() { return wbAccess; } + inline QString getWBValue() { return wbValue; } + inline int getWBDuration() { return wbDuration; } + + // setters + void setValue(const QString& _value); + inline void resetToDefaultValue() { userValue = defaultValue; } + inline void setWBAccess(int _wbAccess) { wbAccess = _wbAccess; } + inline void setWBValue(QString _wbValue) { wbValue = _wbValue; } + inline void setWBDuration(int _wbDuration) { wbDuration = _wbDuration; } + + // testers + bool isValueSet(); + bool isDefaultValue(); + bool isWishboneParameter(); + + // others + BlockParameter* clone(); + QString toVHDL(int context, int flags); + +private: + + QString width; + QVariant userValue; + int wbAccess; + QString wbValue; + int wbDuration; + + +}; + +#endif // __BLOCKPARAMETERWISHBONE_H__ + diff --git a/BlockWidget.cpp b/BlockWidget.cpp new file mode 100644 index 0000000..3b8a658 --- /dev/null +++ b/BlockWidget.cpp @@ -0,0 +1,300 @@ +#include "BlockWidget.h" + +using namespace std; +using namespace Qt; + +BlockWidget::BlockWidget(QWidget *parent) : QWidget(parent) +{ + + rxComment = new QRegExp("(.*)--.*"); + rxComma = new QRegExp("(.*)[;]"); + rxPort = new QRegExp("[\\s\\t]*(.*)[\\s\\t]*:[\\s\\t]*(in|out|inout)[\\s\\t]*(.*)",CaseInsensitive,QRegExp::RegExp); + rxEnt = new QRegExp("[\\s\\t]*entity[\\s\\t]*(.*)[\\s\\t]*is",CaseInsensitive,QRegExp::RegExp); + rxArch = new QRegExp("[\\s\\t]*architecture[\\s\\t]*(.*)[\\s\\t]*of (.*)[\\s\\t]*is",CaseInsensitive,QRegExp::RegExp); + rxComp = new QRegExp("[\\s\\t]*component[\\s\\t]*(.*)[\\s\\t]*",CaseInsensitive,QRegExp::RegExp); + rxEnd = new QRegExp("[\\s\\t]*end(.*)",CaseInsensitive,QRegExp::RegExp); + rxComp = new QRegExp("[\\s\\t]*end component;",CaseInsensitive,QRegExp::RegExp); + rxGeneric = new QRegExp("[\\s\\t]*generic[\\s\\t]*[(][\\s\\t]*",CaseInsensitive,QRegExp::RegExp); + rxEndGen = new QRegExp("[\\s\\t]*[)]",CaseInsensitive,QRegExp::RegExp); + rxGen = new QRegExp("[\\s\\t]*(.*)[\\s\\t]*:[\\s\\t]*(.*)[\\s\\t]*:=[\\s\\t]*(.*)",CaseInsensitive,QRegExp::RegExp); + rxConst = new QRegExp("[\\s\\t]*constant[\\s\\t]*(.*)[\\s\\t]*:[\\s\\t]*(.)*[\\s\\t]*:=[\\s\\t]*(.*)",CaseInsensitive,QRegExp::RegExp); + rxWidth = new QRegExp(".*[(](.*)(downto|to)(.*)[)]",CaseInsensitive,QRegExp::RegExp); + + loadBt = new QPushButton("load VHDL"); + genBt = new QPushButton("generate XML"); + QHBoxLayout *widgetLayout = new QHBoxLayout; + QVBoxLayout *left = new QVBoxLayout; + QVBoxLayout *right = new QVBoxLayout; + + scrollPort = new QScrollArea; + scrollPort->setWidgetResizable(true); + twPort = new QTableWidget(this); + scrollPort->setWidget(twPort); + scrollGen = new QScrollArea; + scrollGen->setWidgetResizable(true); + twGen = new QTableWidget(this); + scrollGen->setWidget(twGen); + teName = new QTextEdit; + teBrief = new QTextEdit; + teDesc = new QTextEdit; + lblName = new QLabel("Block name :"); + lblBrief = new QLabel("Enter a brief description : "); + lblDesc = new QLabel("Enter a detailled description : "); + lblPort = new QLabel("Ports :"); + lblGen = new QLabel("Generics :"); + + connect(loadBt, SIGNAL(clicked()),this, SLOT(loadCode())); + connect(genBt, SIGNAL(clicked()), this, SLOT(generateXml())); + + left->addWidget(loadBt); + left->addWidget(lblPort); + left->addWidget(scrollPort); + left->addWidget(lblGen); + left->addWidget(scrollGen); + + right->addWidget(lblName); + right->addWidget(teName); + right->addWidget(lblBrief); + right->addWidget(teBrief); + right->addWidget(lblDesc); + right->addWidget(teDesc); + right->addWidget(genBt); + + widgetLayout->addLayout(left); + widgetLayout->addLayout(right); + setLayout(widgetLayout); + show(); +} + +BlockWidget::~BlockWidget() +{ + +} + +// This function opens a VHDL file and get the informations about the entity : +// First the generics, then the signals. +// You can edit the descriptions in the right, one for the brief description, the other for the detailled. +void BlockWidget::loadCode() { + + QString line, portName, portType, portId, genName, genType, genValue; + QStringList *portNameList, *portTypeList, *portIdList, *genNameList, *genTypeList, *genValueList; + cpt = 0; + twPort->setColumnCount(3); + twPort->setRowCount(cpt); + twGen->setColumnCount(3); + twGen->setRowCount(cpt); + portNameList = new QStringList; + portTypeList = new QStringList; + portIdList = new QStringList; + genNameList = new QStringList; + genTypeList = new QStringList; + genValueList = new QStringList; + + fileName = QFileDialog::getOpenFileName(this, + tr("Open File"), "C:", tr("Files (*.txt *.vhd)")); + QFile file(fileName); + + if(!file.open(QIODevice::ReadOnly | QIODevice::Text)) + return; + QTextStream ts(&file); + while (!ts.atEnd()) + { + line = ts.readLine(); + if(rxComment->indexIn(line) != -1) { + line = rxComment->cap(1); + } + + if(rxEnt->indexIn(line)!= -1) { + + entName = rxEnt->cap(1); + teName->setText(entName); + QSize size = teName->document()->size().toSize(); + teName->setMaximumSize(size); + + while(rxEnd->indexIn(line) == -1) { + line = ts.readLine(); + if(rxComment->indexIn(line) != -1) { + line = rxComment->cap(1); + } + if(rxComma->indexIn(line) != -1) { + line = rxComma->cap(1); + } + if(rxGeneric->indexIn(line) != -1) { + while(rxEndGen->indexIn(line) == -1) { + line = ts.readLine(); + if(rxComment->indexIn(line) != -1) { + line = rxComment->cap(1); + } + if(rxComma->indexIn(line) != -1) { + line = rxComma->cap(1); + } + if(rxGen->indexIn(line) != -1) { + genName = rxGen->cap(1).simplified(); + genType = rxGen->cap(2).simplified(); + genValue = rxGen->cap(3).simplified(); + + genNameList->append(genName); + genTypeList->append(genType); + genValueList->append(genValue); + } + } + } + if(rxPort->indexIn(line) != -1) { + if(rxComment->indexIn(line) != -1) { + line = rxComment->cap(1); + } + if(rxComma->indexIn(line) != -1) { + line = rxComma->cap(1); + } + portName = rxPort->cap(1).simplified(); + portId = rxPort->cap(2).simplified(); + portType = rxPort->cap(3).simplified(); + portNameList->append(portName); + portIdList->append(portId); + portTypeList->append(portType); + } + } + } + } + + twGen->setRowCount(genNameList->size()); + for(int i = 0; i < genNameList->size(); i++) { + twGen->setItem(i, 0, new QTableWidgetItem(genNameList->at(i))); + twGen->setItem(i, 1, new QTableWidgetItem(genTypeList->at(i))); + twGen->setItem(i, 2, new QTableWidgetItem(genValueList->at(i))); + } + twPort->setRowCount(portNameList->size()); + for(int i = 0; i < portNameList->size(); i++) { + twPort->setItem(i, 0, new QTableWidgetItem(portIdList->at(i))); + twPort->setItem(i, 1, new QTableWidgetItem(portNameList->at(i))); + twPort->setItem(i, 2, new QTableWidgetItem(portTypeList->at(i))); + } + + file.close(); + scrollPort->setWidget(twPort); + return; +} + +// This function gets the informations in the table and the descriptions, and creates a XML file with this content +void BlockWidget::generateXml() { + + QString portName, portType, portId, genName, genType, genValue; + QStringList *portNameList, *portTypeList, *portIdList, *genNameList, *genTypeList, *genValueList; + int x, y, width; + brief = teBrief->toPlainText(); + desc = teDesc->toPlainText(); + entName = teName->toPlainText(); + + portNameList = new QStringList; + portTypeList = new QStringList; + portIdList = new QStringList; + genNameList = new QStringList; + genTypeList = new QStringList; + genValueList = new QStringList; + for(int i = 0; i < twGen->rowCount(); i++) { + genNameList->append(twGen->item(i,0)->text()); + genTypeList->append(twGen->item(i,1)->text()); + genValueList->append(twGen->item(i,2)->text()); + } + + for(int i = 0; i < twPort->rowCount(); i++) { + portIdList->append(twPort->item(i,0)->text()); + portNameList->append(twPort->item(i,1)->text()); + portTypeList->append(twPort->item(i,2)->text()); + } + + QDomDocument doc (entName); + QDomElement block = doc.createElement("block"); + block.setAttribute("name",entName); + block.setAttribute("version", "0.1"); + doc.appendChild(block); + + QDomElement comments = doc.createElement("comments"); + QDomElement category = doc.createElement("caterory"); + category.setAttribute("ids",""); + comments.appendChild(category); + + QDomElement eBrief = doc.createElement("brief"); + if(!brief.isEmpty()) { + QDomText txtBrief = doc.createTextNode(brief); + eBrief.appendChild(txtBrief); + comments.appendChild(eBrief); + } + QDomElement eDesc = doc.createElement("description"); + if(!desc.isEmpty()) { + QDomText txtDesc = doc.createTextNode(desc); + eDesc.appendChild(txtDesc); + comments.appendChild(eDesc); + } + block.appendChild(comments); + + QDomElement parameters = doc.createElement("parameters"); + QDomElement interfaces = doc.createElement("interfaces"); + QDomElement inputs = doc.createElement("inputs"); + QDomElement outputs = doc.createElement("outputs"); + QDomElement bidirs = doc.createElement("bidirs"); + block.appendChild(parameters); + block.appendChild(interfaces); + interfaces.appendChild(inputs); + interfaces.appendChild(outputs); + interfaces.appendChild(bidirs); + + for(int i = 0; i < twGen->rowCount(); i++) { + genName = genNameList->at(i); + genType = genTypeList->at(i); + genValue = genValueList->at(i); + QDomElement parameter = doc.createElement("parameter"); + parameter.setAttribute("name",genName); + parameter.setAttribute("type",genType); + parameter.setAttribute("value",genValue); + parameter.setAttribute("context","generic"); + parameters.appendChild(parameter); + } + + for(int i = 0; i < portIdList->size(); i++) { + portId = portIdList->at(i); + portName = portNameList->at(i); + portType = portTypeList->at(i); + if(rxWidth->indexIn(portType) != -1) { + x = rxWidth->cap(1).toInt(); + y = rxWidth->cap(3).toInt(); + if(x < y) + width = y - x + 1; + else if(x > y) + width = x - y + 1; + else + width = 1; + } + + if(portId.compare("in", CaseInsensitive) == 0) { + QDomElement input = doc.createElement("input"); + input.setAttribute("name",portName); + input.setAttribute("width", width); + inputs.appendChild(input); + } + else if(portId.compare("out", CaseInsensitive) == 0) { + QDomElement output = doc.createElement("output"); + output.setAttribute("name",portName); + output.setAttribute("width", width); + outputs.appendChild(output); + } + else if(portId.compare("inout", CaseInsensitive) == 0) { + QDomElement bidir = doc.createElement("bidir"); + bidir.setAttribute("name",portName); + bidir.setAttribute("width", width); + bidirs.appendChild(bidir); + } + } + + fileName = QFileDialog::getOpenFileName(this, tr("Open File"), + "C:", tr("Files (*.xml)")); + QFile file(fileName); + if(!file.open(QIODevice::WriteOnly | QIODevice::Text)) + return; + QTextStream ts(&file); + doc.save(ts,QDomNode::EncodingFromTextStream); + file.close(); + + QLabel *popup = new QLabel("Votre fichier XML est rempli"); + popup->show(); +} diff --git a/BlockWidget.h b/BlockWidget.h new file mode 100644 index 0000000..9150928 --- /dev/null +++ b/BlockWidget.h @@ -0,0 +1,42 @@ +#ifndef ENTITYWIDGET_H +#define ENTITYWIDGET_H + +#include +#include +#include +#include +#include +#include +#include + +#include "Parameters.h" +#include "BlockParameter.h" +#include "Graph.h" + +class BlockWidget : public QWidget +{ + Q_OBJECT +public: + explicit BlockWidget(QWidget *parent = 0); + ~BlockWidget(); + +private: + QPushButton *loadBt, *genBt; + int cptIn, cptOut, cptInout, cpt; + QRegExp *rxPort, *rxEnt, *rxArch, *rxComp, *rxComment, *rxComma, + *rxEndComp, *rxEnd, *rxGeneric, *rxEndGen, *rxGen, *rxConst, *rxWidth; + QString fileName, txt, s, entName, brief, desc; + QScrollArea *scrollPort, *scrollGen; + QWidget *wid; + QLabel *labelAppli, *lblBrief, *lblDesc, *lblName, *lblPort, *lblGen; + QTableWidget *twPort, *twGen; + QTextEdit *teBrief, *teDesc, *teName; + +signals: + +public slots: + void loadCode(); + void generateXml(); +}; + +#endif // ENTITYWIDGET_H diff --git a/BlocksToConfigureWidget.cpp b/BlocksToConfigureWidget.cpp new file mode 100644 index 0000000..f860e52 --- /dev/null +++ b/BlocksToConfigureWidget.cpp @@ -0,0 +1,77 @@ +#include "BlocksToConfigureWidget.h" +#include +#include "ParametersWindow.h" + + +BlocksToConfigureWidget::BlocksToConfigureWidget(QList blocksList, Parameters *_params, QWidget *parent) : + QWidget(parent) +{ + blocks = blocksList; + params = _params; + layout = new QGridLayout; + tree = new QTreeWidget(this); + configureButton = new QPushButton("configure", this); + configureButton->setEnabled(false); + + connect(tree, SIGNAL(itemClicked(QTreeWidgetItem*,int)), this, SLOT(clicked())); + connect(tree, SIGNAL(itemDoubleClicked(QTreeWidgetItem*,int)), this, SLOT(doubleClicked())); + connect(configureButton, SIGNAL(clicked()), this, SLOT(configure())); + + updateNamesList(); + tree->setHeaderLabel("blocks to configure"); + layout->addWidget(tree); + layout->addWidget(configureButton); + + this->setLayout(layout); + this->setFixedSize(300,230); +} + +void BlocksToConfigureWidget::updateNamesList() +{ + tree->clear(); + QTreeWidgetItem *item = NULL; + foreach(AbstractBlock *block, blocks){ + item = new QTreeWidgetItem(tree->invisibleRootItem()); + item->setData(0, Qt::DisplayRole, block->getName()); + } + +} + +void BlocksToConfigureWidget::updateBlocksList() +{ + blocks = params->getBlocksToConfigure(); + updateNamesList(); +} + +void BlocksToConfigureWidget::closeEvent(QCloseEvent *event) +{ + //when parameters validation is over, + //we start connections validation. + params->connectionsValidation(); +} + +void BlocksToConfigureWidget::clicked() +{ + if(tree->selectedItems().length() > 0) + configureButton->setEnabled(true); + else + configureButton->setEnabled(false); +} + +void BlocksToConfigureWidget::doubleClicked() +{ + configure(); +} + +void BlocksToConfigureWidget::configure() +{ + if(tree->selectedItems().length() > 0){ + bool firstItem = true; // We take only the first selected item + for (int i=0; itopLevelItemCount(); i++){ + if(firstItem && tree->topLevelItem(i)->isSelected()){ + firstItem = false; + new ParametersWindow(blocks.at(i), params, this); + } + } + } +} diff --git a/BlocksToConfigureWidget.h b/BlocksToConfigureWidget.h new file mode 100644 index 0000000..52b31fc --- /dev/null +++ b/BlocksToConfigureWidget.h @@ -0,0 +1,39 @@ +#ifndef __BLOCKSTOCONFIGUREWIDGET_H__ +#define __BLOCKSTOCONFIGUREWIDGET_H__ + +#include + +#include +#include +#include "Parameters.h" +#include "AbstractBlock.h" +#include + +class BlocksToConfigureWidget : public QWidget +{ + Q_OBJECT +public: + BlocksToConfigureWidget(QList blocksList, Parameters *_params, QWidget *parent=0); + + void updateNamesList(); + void updateBlocksList(); + + inline QTreeWidget *getTree() { return tree; } + inline QList getBlocks() { return blocks; } + +private: + void closeEvent(QCloseEvent * event); + + QList blocks; + QTreeWidget *tree; + QGridLayout *layout; + QPushButton *configureButton; + Parameters *params; + +private slots: + void clicked(); + void doubleClicked(); + void configure(); +}; + +#endif // BLOCKSTOCONFIGUREWIDGET_H diff --git a/BoxItem.cpp b/BoxItem.cpp new file mode 100644 index 0000000..77e9533 --- /dev/null +++ b/BoxItem.cpp @@ -0,0 +1,681 @@ +#include "BoxItem.h" +#include "GroupScene.h" +#include "ConnectionItem.h" +#include "InterfaceItem.h" +#include "GroupItem.h" +#include "Parameters.h" +#include "Exception.h" +#include "Dispatcher.h" +#include "FunctionalBlock.h" +#include "FunctionalInterface.h" +#include "ReferenceInterface.h" +#include "ReferenceBlock.h" +#include "ParametersWindow.h" +#include "BlockParameter.h" + + +BoxItem::BoxItem(AbstractBlock *_refBlock, + Dispatcher *_dispatcher, + Parameters *_params, GroupItem *parent) throw(Exception) : AbstractBoxItem( _refBlock, _dispatcher, _params, parent) { + + /* NOTE : + _refBlock : mandatory a FunctionalBlock or a GroupBlock + */ + if (_refBlock->isReferenceBlock()) throw(Exception(BLOCK_INVALID_TYPE)); + + childGroupItem = NULL; + //boxWidth = params->defaultBlockWidth; + //boxHeight = params->defaultBlockHeight; + currentBorder = NoBorder; + selected = false; + + setZValue(100); + setFlags(QGraphicsItem::ItemIsMovable | QGraphicsItem::ItemIsSelectable | QGraphicsItem::ItemSendsGeometryChanges); + + initInterfaces(); + updateGeometry(); + resetInterfacesPosition(); + QPointF initPos = QPointF(0.0,0.0) - originPoint; + setPos(initPos); + //cout << "total size of block: " << totalWidth << "," << totalHeight << endl; + //cout << "pos in group: " << x() << "," << y() << endl; +} + + +BoxItem::~BoxItem() { +} + +void BoxItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) { + QPen pen(Qt::black, 3); + if(selected) + pen.setColor(Qt::red); + + painter->setPen(pen); + painter->setBrush(Qt::yellow); + + painter->drawRect(0,0,boxWidth, boxHeight); + painter->drawText(0,0,boxWidth, boxHeight,Qt::AlignCenter | Qt::TextWordWrap,QString(refBlock->getName())); + foreach(InterfaceItem *inter, interfaces) { + inter->paint(painter); + } +} + +void BoxItem::moveTo(QPointF dest) { + setPos(dest); + currentPosition = dest; +} + +bool BoxItem::isBoxItem() { + return true; +} + +void BoxItem::updateMinimumSize() { + + int maxSouth = 0; + int maxNorth = 0; + int maxEast = 0; + int maxWest = 0; + int nbSouth = nbInterfacesByOrientation(Parameters::South); + int nbNorth = nbInterfacesByOrientation(Parameters::North); + int nbMaxSN = nbNorth; + if (nbSouth > nbNorth) nbMaxSN = nbSouth; + int nbEast = nbInterfacesByOrientation(Parameters::East); + int nbWest = nbInterfacesByOrientation(Parameters::West); + int nbMaxEW = nbEast; + if (nbWest > nbEast) { + nbMaxEW = nbWest; + } + + int ifaceWidth = 0; + int ifaceHeight = 0; + + foreach(InterfaceItem* iface, interfaces) { + ifaceWidth = iface->getNameWidth(); + ifaceHeight = iface->getNameHeight(); + if (iface->getOrientation() == Parameters::South) { + if (ifaceWidth > maxSouth) maxSouth = ifaceWidth; + } + else if (iface->getOrientation() == Parameters::North) { + if (ifaceWidth > maxNorth) maxNorth = ifaceWidth; + } + else if (iface->getOrientation() == Parameters::East) { + if (ifaceWidth > maxEast) maxEast = ifaceWidth; + } + else if (iface->getOrientation() == Parameters::West) { + if (ifaceWidth > maxWest) maxWest = ifaceWidth; + } + } + + /* NB: the width layout is the following + ifaceMargin | maxWest | nameMargin | name | nameMargin | maxEast | ifaceMargin + */ + minimumBoxWidth = maxWest+maxEast+nameWidth+2*(ifaceMargin+nameMargin); + // if the minimum is not sufficent taking into account N/S interfaces + if (minimumBoxWidth < (nbMaxSN*ifaceHeight+ifaceMargin*(nbMaxSN+1))) { + minimumBoxWidth = (nbMaxSN*ifaceHeight+ifaceMargin*(nbMaxSN+1)); + } + minimumBoxHeight = maxNorth+maxSouth+3*ifaceMargin; + if (minimumBoxHeight < (nbMaxEW*ifaceHeight+ifaceMargin*(nbMaxEW+1))) { + minimumBoxHeight = (nbMaxEW*ifaceHeight+ifaceMargin*(nbMaxEW+1)); + } +} + + +/* updateGeometry() : + + */ +bool BoxItem::updateGeometry(ChangeType type) { + + currentPosition = pos(); + //cout << "current pos of block: " << currentPosition.x() << "," << currentPosition.y() << endl; + QPointF oldOrigin = originPoint; + QSize oldSize(totalWidth,totalHeight); + + bool boxSizeChanged = false; + + if ((type == Resize) || (type == InterfaceMove)) { + updateMinimumSize(); + } + + if (type == Resize) { + prepareGeometryChange(); + updateInterfacesAndConnections(); + boxSizeChanged = true; + } + if (boxWidth < minimumBoxWidth) { + boxWidth = minimumBoxWidth; + boxSizeChanged = true; + } + if (boxHeight < minimumBoxHeight) { + boxHeight = minimumBoxHeight; + boxSizeChanged = true; + } + if (boxSizeChanged) { + updateInterfacesAndConnections(); + } + + double x = 0.0; + double y = 0.0; + totalWidth = boxWidth; + totalHeight = boxHeight; + + if(isInterfaces(Parameters::East)){ + totalWidth += params->arrowWidth+params->arrowLineLength; + } + if(isInterfaces(Parameters::West)){ + totalWidth += params->arrowWidth+params->arrowLineLength; + x -= params->arrowWidth+params->arrowLineLength; + } + if(isInterfaces(Parameters::South)){ + totalHeight += params->arrowWidth+params->arrowLineLength; + } + if(isInterfaces(Parameters::North)){ + totalHeight += params->arrowWidth+params->arrowLineLength; + y -= params->arrowWidth+params->arrowLineLength; + } + QSizeF newSize(totalWidth,totalHeight); + originPoint.setX(x); + originPoint.setY(y); + + if ((boxSizeChanged) || (newSize != oldSize) || (originPoint != oldOrigin)) { + prepareGeometryChange(); + return true; + } + return false; +} + +void BoxItem::mouseMoveEvent(QGraphicsSceneMouseEvent *event) { + + if(params->editState == Parameters::EditBlockMove) { + QPointF absPos = currentPosition + originPoint; + int marginConn = 2*(params->arrowWidth+params->arrowLineLength); + int gapX = event->scenePos().x() - cursorPosition.x(); + int gapY = event->scenePos().y() - cursorPosition.y(); + + //cout << "block abs. pos: " << absPos.x() << "," << absPos.y() << " | "; + //cout << "block current. pos: " << currentPosition.x() << "," << currentPosition.y() << " | "; + + if (absPos.x()+gapX < marginConn) { + gapX = marginConn-absPos.x(); + } + if (absPos.y()+gapY < marginConn) { + gapY = marginConn-absPos.y(); + } + //cout << "gap: " << gapX << "," << gapY << endl; + QPointF gap(gapX,gapY); + currentPosition = currentPosition+gap; + setPos(currentPosition); + // update all connections from/to this block + foreach(ConnectionItem *item, getScene()->getConnectionItems()){ + if ((item->getFromInterfaceItem()->getOwner() == this) || (item->getToInterfaceItem()->getOwner() == this)) { + item->setPathes(); + } + } + cursorPosition = event->scenePos(); + + // udpate the groupitem + (getScene()->getGroupItem())->updateShape(); + } + else if(params->editState == Parameters::EditBlockResize) { + + int gapX = event->scenePos().x() - cursorPosition.x(); + int gapY = event->scenePos().y() - cursorPosition.y(); + //cout << "gap: " << gapX << "," << gapY << endl; + switch(currentBorder){ + case BorderEast: { + if(boxWidth+gapX > minimumBoxWidth){ + boxWidth += gapX; + } + break; + } + case BorderSouth: { + if(boxHeight+gapY > minimumBoxHeight){ + boxHeight += gapY; + } + break; + } + case CornerSouthEast: { + if(boxWidth+gapX > minimumBoxWidth){ + boxWidth += gapX; + } + if(boxHeight+gapY > minimumBoxHeight){ + boxHeight += gapY; + } + break; + } + case NoBorder: + cout << "abnormal case while resizing block" << endl; + break; + } + // recompute the geometry of the block and possibly the group item + if (updateGeometry()) { + (getScene()->getGroupItem())->updateShape(); + } + + cursorPosition = event->scenePos(); + } + else if(params->editState == Parameters::EditInterfaceMove) { + prepareGeometryChange(); + deplaceInterface(event->pos()); + // recompute the geometry of the block + if (updateGeometry()) { + cout << "must recompute group item geometry" << endl; + (getScene()->getGroupItem())->updateShape(); + } + // update connection from/to the selected interface + foreach(ConnectionItem *item, getScene()->getConnectionItems()){ + if ((item->getFromInterfaceItem() == currentInterface) || (item->getToInterfaceItem() == currentInterface)) { + item->setPathes(); + } + } + } +} + +void BoxItem::mousePressEvent(QGraphicsSceneMouseEvent *event) { + + QPointF pos = event->pos(); + qreal x = pos.x(); + qreal y = pos.y(); + + //QGraphicsItem::mousePressEvent(event); + + if(event->button() == Qt::RightButton) return; + + int mode = getScene()->getEditionMode(); + + dispatcher->setCurrentGroupWidget(getScene()->getGroupWindow()); + + if ((mode == GroupScene::AddConnection) && (params->cursorState == Parameters::CursorOnInterface)) { + InterfaceItem *inter = getInterfaceFromCursor(x,y); + if (inter != NULL) { + + if (params->editState == Parameters::EditNoOperation) { + getScene()->setSelectedInterface(1,inter); + params->setEditState(Parameters::EditStartConnection); + } + else if (params->editState == Parameters::EditStartConnection) { + if (inter == getScene()->getSelectedInterface(1)) { + params->setEditState(Parameters::EditAbortConnection); + } + else { + getScene()->setSelectedInterface(2,inter); + params->setEditState(Parameters::EditCloseConnection); + } + } + } + } + else if (mode == GroupScene::ItemEdition) { + setZValue(zValue()+100); + if (params->cursorState == Parameters::CursorOnInterface) { + InterfaceItem *inter = getInterfaceFromCursor(x,y); + if (inter != NULL) { + if (inter == currentInterface) { + params->setEditState(Parameters::EditInterfaceDeselect); + } + else { + setFlag(ItemIsMovable, false); + currentInterface = inter; + params->setEditState(Parameters::EditInterfaceMove); + } + } + } + else if (params->cursorState == Parameters::CursorInBlock) { + selected = !selected; + params->setEditState(Parameters::EditBlockMove); + cursorPosition = event->scenePos(); + //cout << "cursor current pos. in scene " << cursorPosition.x() << "," << cursorPosition.y() << endl; + update(); + } + else if (params->cursorState == Parameters::CursorOnBorder) { + setFlag(ItemIsMovable, false); + cursorPosition = event->scenePos(); + params->setEditState(Parameters::EditBlockResize); + } + } +} + +void BoxItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) { + + setZValue(zValue()-100); + + int mode = getScene()->getEditionMode(); + + if (mode == GroupScene::AddConnection) { + + if (params->editState == Parameters::EditStartConnection) { + InterfaceItem* iface = getScene()->getSelectedInterface(1); + iface->selected = true; + update(iface->boundingRect()); + } + else if (params->editState == Parameters::EditAbortConnection) { + InterfaceItem* iface = getScene()->getSelectedInterface(1); + iface->selected = false; + update(iface->boundingRect()); + getScene()->setSelectedInterface(1,NULL); + params->setEditState(Parameters::EditNoOperation); + } + else if (params->editState == Parameters::EditCloseConnection) { + InterfaceItem* iface1 = getScene()->getSelectedInterface(1); + InterfaceItem* iface2 = getScene()->getSelectedInterface(2); + bool ok = dispatcher->connect(iface1,iface2); + if (ok) { + iface1->selected = false; + // no update needed since the whole scene will be repainted + getScene()->setSelectedInterface(1,NULL); + getScene()->setSelectedInterface(2,NULL); + params->setEditState(Parameters::EditNoOperation); + } + else { + getScene()->setSelectedInterface(2,NULL); + params->setEditState(Parameters::EditStartConnection); + } + } + } + else if (mode == GroupScene::ItemEdition) { + currentInterface = NULL; + params->editState = Parameters::EditNoOperation; + setFlag(ItemIsMovable); + } + + QGraphicsItem::mouseReleaseEvent(event); +} + +void BoxItem::hoverMoveEvent(QGraphicsSceneHoverEvent * event) { + + QPointF pos = event->pos(); + qreal x = pos.x(); + qreal y = pos.y(); + currentBorder = NoBorder; + int mode = getScene()->getEditionMode(); + + if (mode == GroupScene::AddConnection) { + InterfaceItem* iface = getInterfaceFromCursor(x,y); + if (iface != NULL) { + params->cursorState = Parameters::CursorOnInterface; + setCursor(Qt::PointingHandCursor); + } + else { + params->cursorState = Parameters::CursorNowhere; + setCursor(Qt::ArrowCursor); + } + } + else if (mode == GroupScene::ItemEdition) { + int marginE = 5; + int marginS = 5; + + InterfaceItem* iface = getInterfaceFromCursor(x,y); + if (iface != NULL) { + params->cursorState = Parameters::CursorOnInterface; + setCursor(Qt::PointingHandCursor); + } + else if ((x>boxWidth-marginE)&&(xcursorState = Parameters::CursorOnBorder; + + if ((y>boxHeight-2*marginS)&&(yboxHeight-marginS)&&(ycursorState = Parameters::CursorOnBorder; + + if ((x>boxWidth-2*marginE)&&(x0) && (x0) && (ycursorState = Parameters::CursorInBlock; + setCursor(Qt::OpenHandCursor); + } + else { + params->cursorState = Parameters::CursorNowhere; + setCursor(Qt::ArrowCursor); + } + } + } + QGraphicsItem::hoverMoveEvent(event); +} + + +void BoxItem::contextMenuEvent(QGraphicsSceneContextMenuEvent * event) { + + QMenu menu; + QAction* removeAction = menu.addAction("Remove"); + QAction* duplicateAction = menu.addAction("Duplicate"); + QAction* renameAction = menu.addAction("Rename"); + QAction* connectToGroup = NULL; + QAction* disconnectFromGroup = NULL; + QAction* showProperties = NULL; + QAction* cloneInterface = NULL; + QAction* openWindow = NULL; + QAction* showRstClkInter = NULL; + QAction* showParameters = NULL; + + InterfaceItem* ifaceItem = getInterfaceFromCursor(event->pos().x(), event->pos().y()); + if( ifaceItem != NULL){ + showProperties = menu.addAction("Show properties"); + + ConnectedInterface* iface = ifaceItem->refInter; + if ((iface->getDirection() == AbstractInterface::Input) && (iface->getConnectedFrom() == NULL)) { + connectToGroup = menu.addAction("Connect to group input"); + } + else if ((iface->getDirection() == AbstractInterface::Output) && (!iface->isConnectedTo())) { + connectToGroup = menu.addAction("Connect to group output"); + } + else if ((iface->getConnectionFromParentGroup() != NULL) || (iface->getConnectionToParentGroup() != NULL)) { + disconnectFromGroup = menu.addAction("Disconnect from group"); + } + + if (iface->isFunctionalInterface()) { + FunctionalInterface* fi = AI_TO_FUN(ifaceItem->refInter); + ReferenceInterface* ri = (ReferenceInterface*)(fi->getReference()); + if(ri->getMultiplicity() == -1 || ri->getMultiplicity() > 1){ + cloneInterface = menu.addAction("Clone interface"); + } + } + } + if(refBlock->isGroupBlock()){ + openWindow = menu.addAction("Open/show group window"); + } else { + showRstClkInter = menu.addAction("Show reset/clock interfaces"); + showRstClkInter->setCheckable(true); + showRstClkInter->setChecked(rstClkVisible); + + showParameters = menu.addAction("Show parameters"); + } + + QAction* selectedAction = NULL; + selectedAction = menu.exec(event->screenPos()); + + if(selectedAction == NULL) return ; + + if (selectedAction == removeAction) { + dispatcher->removeBlock(this); + } + else if (selectedAction == duplicateAction) { + dispatcher->duplicateBlock(this); + } + else if(selectedAction == renameAction){ + if(ifaceItem != NULL) + dispatcher->rename(ifaceItem); + else + dispatcher->rename(this); + } + else if(selectedAction == showProperties){ + dispatcher->showProperties(ifaceItem); + } + else if (selectedAction == connectToGroup){ + dispatcher->connectInterToGroup(ifaceItem); + } + else if (selectedAction == disconnectFromGroup) { + dispatcher->disconnectInterFromGroup(ifaceItem); + } + else if (selectedAction == cloneInterface){ + dispatcher->duplicateInterface(ifaceItem); + } + else if (selectedAction == openWindow){ + dispatcher->showRaiseWindow(this); + } + else if(selectedAction == showRstClkInter){ + dispatcher->showRstClkInter(this); + } + else if(selectedAction == showParameters){ + new ParametersWindow(refBlock, params, NULL); + } +} + +void BoxItem::save(QXmlStreamWriter &writer) { + if (refBlock->isFunctionalBlock()) { + writer.writeStartElement("bi_functional"); + + writer.writeAttribute("id",QString::number(id)); + writer.writeAttribute("ref_xml", ((FunctionalBlock*)refBlock)->getReferenceXmlFile()); + writer.writeAttribute("ref_md5", ((FunctionalBlock*)refBlock)->getReferenceHashMd5()); + writer.writeAttribute("name",refBlock->getName()); + QString attrPos = QString::number(pos().x()).append(",").append(QString::number(pos().y())); + writer.writeAttribute("position",attrPos); + QString attrDim = QString::number(getWidth()).append(",").append(QString::number(getHeight())); + writer.writeAttribute("dimension",attrDim); + + writer.writeStartElement("bif_parameters"); + foreach(BlockParameter *param,refBlock->getParameters()){ + writer.writeStartElement("bif_parameter"); + + writer.writeAttribute("name",param->getName()); + writer.writeAttribute("value",param->getValue().toString()); + /* + writer.writeAttribute("context",param->getStrContext()); + writer.writeAttribute("type",param->getTypeString()); + */ + writer.writeEndElement(); // + } + writer.writeEndElement(); // + + writer.writeStartElement("bif_ifaces"); + writer.writeAttribute("count",QString::number(interfaces.length())); + foreach(InterfaceItem* inter, interfaces){ + writer.writeStartElement("bif_iface"); + + writer.writeAttribute("id",QString::number(inter->getId())); + writer.writeAttribute("name",inter->getName()); + writer.writeAttribute("ref_name",inter->refInter->getName()); + writer.writeAttribute("orientation",inter->getStrOrientation()); + writer.writeAttribute("position",QString::number(inter->getPositionRatio())); + + writer.writeEndElement(); // + } + writer.writeEndElement(); // + + writer.writeEndElement(); // + } + else { + writer.writeStartElement("bi_group"); + + writer.writeAttribute("id",QString::number(id)); + writer.writeAttribute("inside_group",QString::number(childGroupItem->getId())); + QString attrPos = QString::number(pos().x()).append(",").append(QString::number(pos().y())); + writer.writeAttribute("position",attrPos); + QString attrDim = QString::number(getWidth()).append(",").append(QString::number(getHeight())); + writer.writeAttribute("dimension",attrDim); + + writer.writeStartElement("big_ifaces"); + writer.writeAttribute("count",QString::number(interfaces.length())); + foreach(InterfaceItem* inter, interfaces){ + writer.writeStartElement("big_iface"); + + writer.writeAttribute("id",QString::number(inter->getId())); + writer.writeAttribute("ref_name",inter->refInter->getName()); + writer.writeAttribute("orientation",inter->getStrOrientation()); + writer.writeAttribute("position",QString::number(inter->getPositionRatio())); + + writer.writeEndElement(); // + } + + writer.writeEndElement(); // + writer.writeEndElement(); // + } +} + +QDataStream &operator <<(QDataStream &out, BoxItem &b) { + out.setVersion(QDataStream::Qt_4_8); + + QByteArray blockData; + QDataStream toWrite(&blockData, QIODevice::WriteOnly); + + QString refXml = ((FunctionalBlock*)b.refBlock)->getReferenceXmlFile(); + QByteArray xmlFile = QByteArray(refXml.toStdString().c_str()); + toWrite << xmlFile; + + toWrite << b.id; + toWrite << (int)b.x(); + toWrite << (int)b.y(); + toWrite << b.boxWidth; + toWrite << b.boxHeight; + toWrite << b.getInterfaces().length(); + + for(int i=0; igetId(); + toWrite << inter->getName(); + toWrite << inter->getPositionRatio(); + toWrite << inter->getOrientation(); + } + + out << blockData; + + return out; +} + +QDataStream &operator >>(QDataStream &in, BoxItem &b) +{ + + in.setVersion(QDataStream::Qt_4_8); + + int x,y,nbInter; + + in >> b.id; + in >> x; + in >> y; + + b.setX(x); + b.setY(y); + + in >> b.boxWidth; + in >> b.boxHeight; + in >> nbInter; + + cout << "nbInter:" << nbInter << endl; + for(int i=0; i> id; + in >> name; + in >> positionRatio; + in >> orientation; + + inter->setId(id); + inter->setName(name); + inter->setPositionRatio(positionRatio); + inter->setOrientation(orientation); + inter->updatePosition(); + + } + + return in; +} diff --git a/BoxItem.h b/BoxItem.h new file mode 100644 index 0000000..74eb36a --- /dev/null +++ b/BoxItem.h @@ -0,0 +1,83 @@ +#ifndef __BLOCKITEM_H__ +#define __BLOCKITEM_H__ + +#include + +#include +#include + + +#include "AbstractBoxItem.h" +class AbstractBoxItem; + +class GroupItem; +class Parameters; +class Dispacther; + +#include "Exception.h" + +using namespace std; +using namespace Qt; + +/* NOTE : + + A BoxItem may represent a functional block or a group block within a group item, itslef + within a scene. What says what it represents is refBlock, i.e. if refBlock->isFunctionalBlock() + or refBlock->isGroupBlock() returns true. + */ +class BoxItem : public AbstractBoxItem { + +public: + BoxItem(AbstractBlock *_refBlock, Dispatcher *_dispatcher, Parameters *_params, GroupItem* parent) throw(Exception); + ~BoxItem(); + + void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = 0); + + // getters + inline GroupItem *getChildGroupItem() { return childGroupItem; } + + // setters + inline void setChildGroupItem(GroupItem* item) { childGroupItem = item; } + + // testers + bool isBoxItem(); + + // others + void moveTo(QPointF dest); + void save(QXmlStreamWriter& writer); + +protected: + + void updateMinimumSize(); // modify the minimum size + bool updateGeometry(ChangeType type); // modify the originPoint and the total dimension + + void mousePressEvent(QGraphicsSceneMouseEvent *event); + void mouseReleaseEvent(QGraphicsSceneMouseEvent *event); + void mouseMoveEvent(QGraphicsSceneMouseEvent *event); + void contextMenuEvent(QGraphicsSceneContextMenuEvent *event); + void hoverMoveEvent( QGraphicsSceneHoverEvent *event); + +private: + + /* NOTE : + A BlockItem is always graphically within a GroupItem, inside the same scene. + + A BlockItem may also be the graphical view of a GroupBlock. In this case, there exists a child scene + containing a GroupItem. insideGroup atribute refers to this GroupItem and thus, may be NULL if the current + blockItem represents a functional block + */ + GroupItem *childGroupItem; + + + friend QDataStream &operator<<(QDataStream &out, BoxItem &b); + friend QDataStream &operator>>(QDataStream &in, BoxItem &b); + +signals: + void itemMustBeDeleted(QGraphicsItem*); + +}; + +QDataStream & operator <<(QDataStream &out, BoxItem &b); +QDataStream & operator >>(QDataStream &in, BoxItem &b); + +#endif // __BLOCKITEM_H__ diff --git a/ConnectedInterface.cpp b/ConnectedInterface.cpp new file mode 100644 index 0000000..fbfa785 --- /dev/null +++ b/ConnectedInterface.cpp @@ -0,0 +1,68 @@ +#include "ArithmeticEvaluator.h" +#include "ConnectedInterface.h" +#include "FunctionalBlock.h" +#include "GroupBlock.h" + + +ConnectedInterface::ConnectedInterface(AbstractBlock* _owner) : AbstractInterface(_owner) { + connectedFrom = NULL; +} + +ConnectedInterface::ConnectedInterface(AbstractBlock* _owner, const QString& _name, const QString& _type, const QString& _width, int _direction, int _purpose, int _level) : AbstractInterface(_owner, _name, _type, _width, _direction, _purpose, _level) { + connectedFrom = NULL; +} + +void ConnectedInterface::removeConnectedTo(ConnectedInterface *iface) { + connectedTo.removeOne(iface); +} + +void ConnectedInterface::clearConnections() { + connectedFrom = NULL; + connectedTo.clear(); +} + +void ConnectedInterface::clearConnectedTo() { + connectedTo.clear(); +} + +bool ConnectedInterface::connectTo(ConnectedInterface *iface) { + + if (canConnectTo(iface)) { + connectedTo.append(iface); + return true; + } + + return false; +} + +bool ConnectedInterface::connectFrom(ConnectedInterface *iface) { + if (canConnectFrom(iface)) { + connectedFrom = iface; + return true; + } + return false; +} + +/* getConnectionToParentGroup() : + if an interface among connectedTo is an interface of the parent group + returns it. +*/ +ConnectedInterface* ConnectedInterface::getConnectionToParentGroup() { + foreach(ConnectedInterface *iface, connectedTo) { + if (owner->getParent() == iface->owner) { + return iface; + } + } + return NULL; +} + +/* getConnectionFromParentGroup() : + if connectedFrom is an interface of the parent group + returns it. +*/ +ConnectedInterface *ConnectedInterface::getConnectionFromParentGroup() { + if ((connectedFrom != NULL) && (owner->getParent() == connectedFrom->owner)) { + return connectedFrom; + } + return NULL; +} diff --git a/ConnectedInterface.h b/ConnectedInterface.h new file mode 100644 index 0000000..968a00a --- /dev/null +++ b/ConnectedInterface.h @@ -0,0 +1,58 @@ +#ifndef __CONNECTEDINTERFACE_H__ +#define __CONNECTEDINTERFACE_H__ + +#include + +#include +#include + +#include "AbstractInterface.h" +class ReferenceInterface; + +#include "Exception.h" + +using namespace std; +using namespace Qt; + + +class ConnectedInterface : public AbstractInterface { + +public : + + ConnectedInterface(AbstractBlock* _owner); + ConnectedInterface(AbstractBlock* _owner, const QString& _name, const QString& _type, const QString& _width, int _direction, int _purpose, int _level); + // getters + inline QList getConnectedTo() { return connectedTo;} + inline ConnectedInterface* getConnectedFrom() { return connectedFrom;} + + // setters + + // testers + inline bool isConnectedTo(){return connectedTo.length() != 0;} + inline bool isConnectedFrom(){return connectedFrom != NULL;} + virtual bool canConnectTo(AbstractInterface* iface) = 0; + virtual bool canConnectFrom(AbstractInterface* iface) = 0; + + // others + bool connectTo(ConnectedInterface* iface); + bool connectFrom(ConnectedInterface* iface); + ConnectedInterface* getConnectionToParentGroup(); + ConnectedInterface* getConnectionFromParentGroup(); + + virtual AbstractInterface *clone() = 0; + + void removeConnectedTo(ConnectedInterface *inter); + + virtual void clearConnectedTo(); + inline void clearConnectedFrom() { connectedFrom = NULL; } + virtual void clearConnections(); + //virtual void connectionsValidation(QStack *interfacetoValidate, QList *validatedInterfaces) throw(Exception) = 0; + +protected: + QList connectedTo; + ConnectedInterface* connectedFrom; + +}; + + +#endif // __CONNECTEDINTERFACE_H__ diff --git a/ConnectionItem.cpp b/ConnectionItem.cpp new file mode 100644 index 0000000..dee71ba --- /dev/null +++ b/ConnectionItem.cpp @@ -0,0 +1,762 @@ +#include "ConnectionItem.h" + +#include "Dispatcher.h" +#include "Parameters.h" +#include "AbstractBoxItem.h" +#include "ConnectedInterface.h" +#include "InterfaceItem.h" +#include "AbstractBlock.h" + +//int ConnectionItem::counter = 0; + +ConnectionItem::ConnectionItem(InterfaceItem* _iface1, + InterfaceItem* _iface2, + Dispatcher* _dispatcher, + Parameters* _params, + QGraphicsItem *_parent) : QGraphicsItem(_parent) { + + + dispatcher = _dispatcher; + params = _params; + + ConnectedInterface* ref1 = _iface1->refInter; + ConnectedInterface* ref2 = _iface2->refInter; + /* ref. note in .h + case 1 : ref1 is group interface, and ref2 block interface of a block within the group + case 2 : the opposite of case 1 + case 3 : ref1 and ref2 are block interface of blocks that are both within the same parent group. + */ + if (ref1->getOwner() == ref2->getOwner()->getParent()) { + + if (ref1->getDirection() == AbstractInterface::Input) { + fromInterfaceItem = _iface1; + toInterfaceItem = _iface2; + } + else if (ref1->getDirection() == AbstractInterface::InOut) { + fromInterfaceItem = _iface1; + toInterfaceItem = _iface2; + } + else if (ref1->getDirection() == AbstractInterface::Output) { + toInterfaceItem = _iface1; + fromInterfaceItem = _iface2; + } + } + else if (ref1->getOwner()->getParent() == ref2->getOwner()) { + + if (ref1->getDirection() == AbstractInterface::Input) { + fromInterfaceItem = _iface2; + toInterfaceItem = _iface1; + } + else if (ref1->getDirection() == AbstractInterface::InOut) { + fromInterfaceItem = _iface2; + toInterfaceItem = _iface1; + } + else if (ref1->getDirection() == AbstractInterface::Output) { + toInterfaceItem = _iface2; + fromInterfaceItem = _iface1; + } + } + // NB : this case is in fact similar to the previous. Kept here for clarity + else if (ref1->getOwner()->getParent() == ref2->getOwner()->getParent()) { + + if (ref1->getDirection() == AbstractInterface::Input) { + fromInterfaceItem = _iface2; + toInterfaceItem = _iface1; + } + else if (ref1->getDirection() == AbstractInterface::InOut) { + fromInterfaceItem = _iface2; + toInterfaceItem = _iface1; + } + else if (ref1->getDirection() == AbstractInterface::Output) { + toInterfaceItem = _iface2; + fromInterfaceItem = _iface1; + } + } + // adding this to interface items + fromInterfaceItem->addConnectionItem(this); + toInterfaceItem->addConnectionItem(this); + + selected = false; + marginConn = params->arrowLineLength+params->arrowWidth; + + setFlag(ItemIsSelectable); + setAcceptHoverEvents(true); + setFlag(ItemSendsGeometryChanges); + setCursor(Qt::PointingHandCursor); + setZValue(0); + + setPathes(); +} + + +ConnectionItem::ConnectionItem(const ConnectionItem ©) { + pointFrom = copy.pointFrom; + pointTo = copy.pointTo; + interPoint1 = copy.interPoint1; + interPoint2 = copy.interPoint2; + interPoint3 = copy.interPoint3; + interPoint4 = copy.interPoint4; +} + +ConnectionItem::~ConnectionItem() { +} + +ConnectionItem::ConnectionItem() { +} + +QPainterPath ConnectionItem::shape() const { + return pathShape; +} + +QRectF ConnectionItem::boundingRect() const { + + QPointF start, end; + + if(pointFrom.x() < pointTo.x()){ + start.setX(pointFrom.x()-20); + end.setX(pointTo.x()+20); + } else { + start.setX(pointTo.x()-20); + end.setX(pointFrom.x()+20); + } + if(pointFrom.y() < pointTo.y()){ + start.setY(pointFrom.y()-20); + end.setY(pointTo.y()+20); + } else { + start.setY(pointTo.y()-20); + end.setY(pointFrom.y()+20); + } + return QRectF(start, end); +} + +void ConnectionItem::paint(QPainter *painter, + const QStyleOptionGraphicsItem *option, + QWidget *widget) { + + painter->setPen(Qt::blue); + if(selected){ + painter->setPen(Qt::red); + } + + painter->drawPath(pathPaint); +} + +void ConnectionItem::addInterPoint(QPointF point) { + +} + +void ConnectionItem::setPathes() { + + prepareGeometryChange(); + + pointFrom = fromInterfaceItem->getEndPointInGroup(); + pointTo = toInterfaceItem->getEndPointInGroup(); + + int oriFrom, oriTo; + oriFrom = fromInterfaceItem->getOrientation(); + oriTo = toInterfaceItem->getOrientation(); + +/* NB: if src or dest is onwed by a GroupItem the orientation + must be changed as it is a block. + */ + if(fromInterfaceItem->owner->isGroupItem()){ + switch(fromInterfaceItem->getOrientation()){ + case Parameters::North : + oriFrom = Parameters::South; + break; + case Parameters::South : + oriFrom = Parameters::North; + break; + case Parameters::East : + oriFrom = Parameters::West; + break; + case Parameters::West : + oriFrom = Parameters::East; + break; + } + } + if(toInterfaceItem->owner->isGroupItem()){ + switch(toInterfaceItem->getOrientation()){ + case Parameters::North : + oriTo = Parameters::South; + break; + case Parameters::South : + oriTo = Parameters::North; + break; + case Parameters::East : + oriTo = Parameters::West; + break; + case Parameters::West : + oriTo = Parameters::East; + break; + } + } + double gap1 = 0.0; + double gap2 = 0.0; + + if(oriFrom == Parameters::South) { + + // FROM SOUTH TO SOUTH + if(oriTo == Parameters::South) { + computeElle(oriFrom); + } + // FROM SOUTH TO NORTH + else if(oriTo == Parameters::North) { + gap1 = pointTo.y() - pointFrom.y(); + if (gap1 > 2*marginConn) { + computeStaircase(oriFrom); + } + else { + computeEsse(oriFrom); + } + } + // FROM SOUTH TO EAST OR WEST + else if ((oriTo == Parameters::East) || (oriTo == Parameters::West)){ + + gap1 = pointTo.x() - pointFrom.x(); + if (oriTo == Parameters::West) gap1 = -gap1; + gap2 = pointTo.y() - pointFrom.y(); + + if (gap1 > 0.0) { + if (gap2 > 0.0) { + computeHookSmallEnd(oriFrom,oriTo); + } + else { + computeOpenRect(oriFrom,oriTo); + } + } + else { + if (gap2 >= 0.0) { + computeCorner(oriFrom); + } + else { + computeHookSmallStart(oriFrom,oriTo); + } + } + } + } + else if(oriFrom == Parameters::North) { + + // FROM NORTH TO SOUTH + if(oriTo == Parameters::South) { + gap1 = pointFrom.y() - pointTo.y(); + if (gap1 > 2*marginConn) { + computeStaircase(oriFrom); + } + else { + computeEsse(oriFrom); + } + } + // FROM NORTH TO NORTH + else if(oriTo == Parameters::North) { + computeElle(oriFrom); + } + // FROM NORTH TO EAST OR WEST + else if ((oriTo == Parameters::East) || (oriTo == Parameters::West)){ + + gap1 = pointTo.x() - pointFrom.x(); + if (oriTo == Parameters::West) gap1 = -gap1; + gap2 = pointFrom.y() - pointTo.y(); + + if (gap1 > 0.0) { + if (gap2 > 0.0) { + computeHookSmallEnd(oriFrom,oriTo); + } + else { + computeOpenRect(oriFrom,oriTo); + } + } + else { + if (gap2 >= 0.0) { + computeCorner(oriFrom); + } + else { + computeHookSmallStart(oriFrom,oriTo); + } + } + } + } + else if(oriFrom == Parameters::East) { + // FROM EAST TO NORTH OR SOUTH + if ((oriTo == Parameters::South) || (oriTo == Parameters::North)){ + + gap1 = pointFrom.x() - pointTo.x(); + gap2 = pointFrom.y() - pointTo.y(); + if (oriTo == Parameters::North) gap2 = -gap2; + + if (gap1 > 0.0) { + if (gap2 > 0.0) { + computeHookSmallStart(oriFrom,oriTo); + } + else { + computeOpenRect(oriFrom,oriTo); + } + } + else { + if (gap2 >= 0.0) { + computeCorner(oriFrom); + } + else { + computeHookSmallEnd(oriFrom,oriTo); + } + } + } + else if(oriTo == Parameters::East) { + computeElle(oriFrom); + } + else if (oriTo == Parameters::West) { + gap1 = pointTo.x() - pointFrom.x(); + if (gap1 > 2*marginConn) { + computeStaircase(oriFrom); + } + else { + computeEsse(oriFrom); + } + } + } + else if (oriFrom == Parameters::West) { + + // FROM WEST TO NORTH OR SOUTH + if ((oriTo == Parameters::South) || (oriTo == Parameters::North)){ + + gap1 = pointTo.x() - pointFrom.x(); + gap2 = pointFrom.y() - pointTo.y(); + if (oriTo == Parameters::North) gap2 = -gap2; + + if (gap1 > 0.0) { + if (gap2 > 0.0) { + computeHookSmallStart(oriFrom,oriTo); + } + else { + computeOpenRect(oriFrom,oriTo); + } + } + else { + if (gap2 >= 0.0) { + computeCorner(oriFrom); + } + else { + computeHookSmallEnd(oriFrom,oriTo); + } + } + } + else if(oriTo == Parameters::East) { + gap1 = pointFrom.x() - pointTo.x(); + if (gap1 > 2*marginConn) { + computeStaircase(oriFrom); + } + else { + computeEsse(oriFrom); + } + } + else if (oriTo == Parameters::West) { + computeElle(oriFrom); + } + } + + pps.setWidth(5); + pathShape = pps.createStroke(pathPaint); +} + + +void ConnectionItem::computeEsse(int orientationFrom) { + + //cout << "drawing an esse" << endl; + pathPaint = QPainterPath(pointFrom); + interPoints.clear(); + double gap = marginConn; + if ((orientationFrom == Parameters::North)||(orientationFrom == Parameters::West)) gap = -gap; + QPointF p(0.0,0.0); + + if ((orientationFrom == Parameters::East)||(orientationFrom == Parameters::West)) { + // must draw a complete esse + p = QPointF(pointFrom.x()+gap,pointFrom.y()); + pathPaint.lineTo(p); + interPoints.append(p); + p = QPointF(pointFrom.x()+gap,(pointFrom.y()+pointTo.y())/2.0); + pathPaint.lineTo(p); + interPoints.append(p); + p = QPointF(pointTo.x()-gap,(pointFrom.y()+pointTo.y())/2.0); + pathPaint.lineTo(p); + interPoints.append(p); + p = QPointF(pointTo.x()-gap,pointTo.y()); + pathPaint.lineTo(p); + interPoints.append(p); + pathPaint.lineTo(pointTo); + } + else if ((orientationFrom == Parameters::South)||(orientationFrom == Parameters::North)) { + + // must draw a complete esse + p = QPointF(pointFrom.x(),pointFrom.y()+gap); + pathPaint.lineTo(p); + interPoints.append(p); + p = QPointF((pointFrom.x()+pointTo.x())/2.0,pointFrom.y()+gap); + pathPaint.lineTo(p); + interPoints.append(p); + p = QPointF((pointFrom.x()+pointTo.x())/2.0,pointTo.y()-gap); + pathPaint.lineTo(p); + interPoints.append(p); + p = QPointF(pointTo.x(), pointTo.y()-gap); + pathPaint.lineTo(p); + interPoints.append(p); + pathPaint.lineTo(pointTo); + } +} + +void ConnectionItem::computeStaircase(int orientationFrom) { + + pathPaint = QPainterPath(pointFrom); + interPoints.clear(); + QPointF p(0.0,0.0); + + if ((orientationFrom == Parameters::East)||(orientationFrom == Parameters::West)) { + if (pointFrom.y() == pointTo.y()) { + //cout << "drawing straight line" << endl; + pathPaint.lineTo(pointTo); + } + else { + //cout << "drawing a staircase" << endl; + // sufficient place to draw a simple staircase + p = QPointF((pointFrom.x()+pointTo.x())/2.0,pointFrom.y()); + pathPaint.lineTo(p); + interPoints.append(p); + p = QPointF((pointFrom.x()+pointTo.x())/2.0,pointTo.y()); + pathPaint.lineTo(p); + interPoints.append(p); + pathPaint.lineTo(pointTo); + } + } + else if ((orientationFrom == Parameters::South)||(orientationFrom == Parameters::North)) { + if (pointFrom.x() == pointTo.x()) { + //cout << "drawing straight line" << endl; + pathPaint.lineTo(pointTo); + } + else { + //cout << "drawing a staircase" << endl; + // sufficient place to draw a simple staircase + p = QPointF(pointFrom.x(),(pointFrom.y()+pointTo.y())/2.0); + pathPaint.lineTo(p); + interPoints.append(p); + p = QPointF(pointTo.x(),(pointFrom.y()+pointTo.y())/2.0); + pathPaint.lineTo(p); + interPoints.append(p); + pathPaint.lineTo(pointTo); + } + } +} + +/* drawCorner(): + + A Corner has the following shape : + | + |__ + +*/ +void ConnectionItem::computeCorner(int orientationFrom) { + + pathPaint = QPainterPath(pointFrom); + interPoints.clear(); + QPointF p(0.0,0.0); + //cout << "drawing a corner" << endl; + + if ((orientationFrom == Parameters::East)||(orientationFrom == Parameters::West)) { + p = QPointF(pointTo.x(),pointFrom.y()); + pathPaint.lineTo(p); + interPoints.append(p); + pathPaint.lineTo(pointTo); + } + else if ((orientationFrom == Parameters::South)||(orientationFrom == Parameters::North)) { + p = QPointF(pointFrom.x(),pointTo.y()); + pathPaint.lineTo(p); + interPoints.append(p); + pathPaint.lineTo(pointTo); + } +} + +/* drawOpenRect(): + + A OpenRect has the following shape : + __ + | + |_| +*/ +void ConnectionItem::computeOpenRect(int orientationFrom, int orientationTo) { + pathPaint = QPainterPath(pointFrom); + interPoints.clear(); + QPointF p(0.0,0.0); + double gap1 = marginConn; + double gap2 = marginConn; + //cout << "drawing an OpenRect" << endl; + + if ((orientationFrom == Parameters::East)||(orientationFrom == Parameters::West)) { + if (orientationFrom == Parameters::West) { + gap1 = -gap1; + } + if (orientationTo == Parameters::North) { + gap2 = -gap2; + } + p = QPointF(pointFrom.x()+gap1,pointFrom.y()); + pathPaint.lineTo(p); + interPoints.append(p); + p = QPointF(pointFrom.x()+gap1,pointTo.y()+gap2); + pathPaint.lineTo(p); + interPoints.append(p); + p = QPointF(pointTo.x(),pointTo.y()+gap2); + pathPaint.lineTo(p); + interPoints.append(p); + pathPaint.lineTo(pointTo); + + } + else if ((orientationFrom == Parameters::South)||(orientationFrom == Parameters::North)) { + if (orientationFrom == Parameters::North) { + gap1 = -gap1; + } + if (orientationTo == Parameters::West) { + gap2 = -gap2; + } + p = QPointF(pointFrom.x(),pointFrom.y()+gap1); + pathPaint.lineTo(p); + interPoints.append(p); + p = QPointF(pointTo.x()+gap2,pointFrom.y()+gap1); + pathPaint.lineTo(p); + interPoints.append(p); + p = QPointF(pointTo.x()+gap2,pointTo.y()); + pathPaint.lineTo(p); + interPoints.append(p); + pathPaint.lineTo(pointTo); + } +} + +/* drawHookSmallEnd(): + + A Hook has the following shape : + _ + | + |_| + Its end has always a size of marginConn +*/ +void ConnectionItem::computeHookSmallEnd(int orientationFrom, int orientationTo) { + pathPaint = QPainterPath(pointFrom); + interPoints.clear(); + QPointF p(0.0,0.0); + double gap = marginConn; + //cout << "drawing a Hook with small end" << endl; + + if ((orientationFrom == Parameters::East)||(orientationFrom == Parameters::West)) { + + if (orientationTo == Parameters::North) gap = -gap; + + p = QPointF((pointFrom.x()+pointTo.x())/2.0,pointFrom.y()); + pathPaint.lineTo(p); + interPoints.append(p); + p = QPointF((pointFrom.x()+pointTo.x())/2.0,pointTo.y()+gap); + pathPaint.lineTo(p); + interPoints.append(p); + p = QPointF(pointTo.x(),pointTo.y()+gap); + pathPaint.lineTo(p); + interPoints.append(p); + pathPaint.lineTo(pointTo); + + } + else if ((orientationFrom == Parameters::South)||(orientationFrom == Parameters::North)) { + + if (orientationTo == Parameters::West) gap = -gap; + + p = QPointF(pointFrom.x(),(pointFrom.y()+pointTo.y())/2.0); + pathPaint.lineTo(p); + interPoints.append(p); + p = QPointF(pointTo.x()+gap,(pointFrom.y()+pointTo.y())/2.0); + pathPaint.lineTo(p); + interPoints.append(p); + p = QPointF(pointTo.x()+gap,pointTo.y()); + pathPaint.lineTo(p); + interPoints.append(p); + pathPaint.lineTo(pointTo); + } +} + +/* drawHookSmallStart(): + + A Hook has the following shape : + _ + | + |_| + Its start has always a size of marginConn +*/ +void ConnectionItem::computeHookSmallStart(int orientationFrom, int orientationTo) { + pathPaint = QPainterPath(pointFrom); + interPoints.clear(); + QPointF p(0.0,0.0); + double gap = marginConn; + //cout << "drawing a Hook with small start" << endl; + + if ((orientationFrom == Parameters::East)||(orientationFrom == Parameters::West)) { + + if (orientationFrom == Parameters::West) gap = -gap; + + p = QPointF(pointFrom.x()+gap,pointFrom.y()); + pathPaint.lineTo(p); + interPoints.append(p); + p = QPointF(pointFrom.x()+gap,(pointFrom.y()+pointTo.y())/2.0); + pathPaint.lineTo(p); + interPoints.append(p); + p = QPointF(pointTo.x(),(pointFrom.y()+pointTo.y())/2.0); + pathPaint.lineTo(p); + interPoints.append(p); + pathPaint.lineTo(pointTo); + } + else if ((orientationFrom == Parameters::South)||(orientationFrom == Parameters::North)) { + + if (orientationFrom == Parameters::North) gap = -gap; + + p = QPointF(pointFrom.x(),pointFrom.y()+gap); + pathPaint.lineTo(p); + interPoints.append(p); + p = QPointF((pointFrom.x()+pointTo.x())/2.0,pointFrom.y()+gap); + pathPaint.lineTo(p); + interPoints.append(p); + p = QPointF((pointFrom.x()+pointTo.x())/2.0,pointTo.y()); + pathPaint.lineTo(p); + interPoints.append(p); + pathPaint.lineTo(pointTo); + } +} + +/* drawElle(): + + An Elle has the following shape : + | + |_| + +*/ +void ConnectionItem::computeElle(int orientationFrom) { + + pathPaint = QPainterPath(pointFrom); + interPoints.clear(); + QPointF p(0.0,0.0); + double x; + double y; + switch(orientationFrom){ + case Parameters::North : + if(pointFrom.y() < pointTo.y()) { + y = pointFrom.y()-marginConn; + } + else { + y = pointTo.y()-marginConn; + } + p = QPointF(pointFrom.x(),y); + pathPaint.lineTo(p); + interPoints.append(p); + p = QPointF(pointTo.x(),y); + pathPaint.lineTo(p); + interPoints.append(p); + pathPaint.lineTo(pointTo); + break; + case Parameters::South : + if(pointFrom.y() > pointTo.y()) { + y = pointFrom.y()+marginConn; + } + else { + y = pointTo.y()+marginConn; + } + p = QPointF(pointFrom.x(),y); + pathPaint.lineTo(p); + interPoints.append(p); + p = QPointF(pointTo.x(),y); + pathPaint.lineTo(p); + interPoints.append(p); + pathPaint.lineTo(pointTo); + break; + case Parameters::West : + if(pointFrom.x() < pointTo.x()) { + x = pointFrom.x()-marginConn; + } + else { + x = pointTo.x()-marginConn; + } + p = QPointF(x, pointFrom.y()); + pathPaint.lineTo(p); + interPoints.append(p); + p = QPointF(x, pointTo.y()); + pathPaint.lineTo(p); + interPoints.append(p); + pathPaint.lineTo(pointTo); + break; + case Parameters::East : + if(pointFrom.x() > pointTo.x()) { + x = pointFrom.x()+marginConn; + } + else { + x = pointTo.x()+marginConn; + } + p = QPointF(x, pointFrom.y()); + pathPaint.lineTo(p); + interPoints.append(p); + p = QPointF(x, pointTo.y()); + pathPaint.lineTo(p); + interPoints.append(p); + pathPaint.lineTo(pointTo); + break; + } +} + +void ConnectionItem::setSelected(bool selected) { + this->selected = selected; + if(selected){ + setZValue(50); + } else { + setZValue(0); + } +} + +void ConnectionItem::mousePressEvent(QGraphicsSceneMouseEvent *event) { + QGraphicsItem::mousePressEvent(event); + setZValue(zValue()+100); + setSelected(!selected); + update(boundingRect()); +} + +void ConnectionItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) { + QGraphicsItem::mouseReleaseEvent(event); + setZValue(zValue()-100); +} + +void ConnectionItem::mouseMoveEvent(QGraphicsSceneMouseEvent *event) { + QGraphicsItem::mouseMoveEvent(event); +} + +void ConnectionItem::contextMenuEvent(QGraphicsSceneContextMenuEvent * event) { + QMenu menu; + QAction* removeAction = menu.addAction("Remove"); + QAction * selectedAction= menu.exec(event->screenPos()); + + if(selectedAction == removeAction){ + dispatcher->removeConnection(this); + dispatcher->removeUselessGroupInterfaces(); + } +} + +void ConnectionItem::prepareChange() { + prepareGeometryChange(); +} + +QDataStream &operator <<(QDataStream &out, ConnectionItem &c) { + out.setVersion(QDataStream::Qt_4_8); + + QByteArray connData; + QDataStream toWrite(&connData, QIODevice::WriteOnly); + + toWrite << c.id; + toWrite << c.getFromInterfaceItem()->getId(); + toWrite << c.getToInterfaceItem()->getId(); + + out << connData; + + return out; +} + +QDataStream &operator >>(QDataStream &in, ConnectionItem &c) { + in.setVersion(QDataStream::Qt_4_8); + + return in; +} diff --git a/ConnectionItem.h b/ConnectionItem.h new file mode 100644 index 0000000..567a31d --- /dev/null +++ b/ConnectionItem.h @@ -0,0 +1,107 @@ +#ifndef __CONNECTIONITEM_H__ +#define __CONNECTIONITEM_H__ + +#include + +#include +#include +#include + +class Dispatcher; +class Parameters; +class InterfaceItem; + +using namespace std; +using namespace Qt; + +/* NOTES : + + A connection item represent a graphical link between two interface items. + Even if it links two in/out interfaces, it is always oriented. + The orientation depends on the type and direction of linked interfaces : + + If interfaces are owned by blocks (group or func) that are within the same + parent group, then src must be an output, and dest an input, or both are in/out + and src/dest may be interchanged. + + If one interface I1 is owend by a block, and the other I2 by the parent group of that block, + then they have the same direction. If this direction is input, then src = I2 and dest = I1, + if it is output, then src = I1, dest = I2, and if it is in/out, no order matters. + + In order to simplify other methods, the constructor of ConnectionItem + checks these cases in order to retrieve the good src and dest if they are + not provided in the good order. + + */ +class ConnectionItem : public QGraphicsItem { + +public: + + ConnectionItem(InterfaceItem* _iface1, + InterfaceItem* _iface2, + Dispatcher* _dispatcher, + Parameters* _params, + QGraphicsItem* _parent); + ConnectionItem (const ConnectionItem & copy); + ConnectionItem(); + ~ConnectionItem(); + + QRectF boundingRect() const; + QPainterPath shape() const; + + void prepareChange(); + + inline InterfaceItem* getToInterfaceItem(){ return toInterfaceItem; } + inline void setToInterfaceItem(InterfaceItem *iface){ toInterfaceItem = iface; } + inline InterfaceItem* getFromInterfaceItem(){ return fromInterfaceItem; } + inline void setFromInterfaceItem(InterfaceItem* iface){ fromInterfaceItem = iface; } + inline int getId(){ return id; } + inline void setId(int id){ this->id = id; } + inline bool isSelected() { return selected; } + void setSelected(bool selected); + + void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = 0); + void setPathes(); + void addInterPoint(QPointF point); + + static int counter; + +protected: + void mousePressEvent(QGraphicsSceneMouseEvent *event); + void mouseReleaseEvent(QGraphicsSceneMouseEvent *event); + void mouseMoveEvent(QGraphicsSceneMouseEvent *event); + void contextMenuEvent(QGraphicsSceneContextMenuEvent *event); + +private: + + QPointF pointFrom; + QPointF pointTo; + QList interPoints; + QPointF interPoint1; + QPointF interPoint2; + QPointF interPoint3; + QPointF interPoint4; + + QPainterPath pathPaint; + QPainterPath pathShape; + QPainterPathStroker pps; + Dispatcher* dispatcher; + Parameters* params; + InterfaceItem* fromInterfaceItem; + InterfaceItem* toInterfaceItem; + bool selected; + int id; + int marginConn; + void computeEsse(int orientationFrom); + void computeStaircase(int orientationFrom); + void computeHookSmallEnd(int orientationFrom, int orientationTo); + void computeHookSmallStart(int orientationFrom, int orientationTo); + void computeOpenRect(int orientationFrom, int orientationTo); + void computeElle(int orientationFrom); + void computeCorner(int orientationFrom); + + friend QDataStream &operator << (QDataStream &out, ConnectionItem &c); + friend QDataStream &operator >> (QDataStream &in, ConnectionItem &c); +}; + +#endif // diff --git a/Dispatcher.cpp b/Dispatcher.cpp new file mode 100644 index 0000000..a85b942 --- /dev/null +++ b/Dispatcher.cpp @@ -0,0 +1,832 @@ +#include "Dispatcher.h" +#include "Parameters.h" +#include "MainWindow.h" + +#include "Graph.h" +#include "ReferenceBlock.h" +#include "GroupBlock.h" +#include "FunctionalBlock.h" + +#include "ConnectedInterface.h" +#include "ReferenceInterface.h" +#include "GroupInterface.h" +#include "FunctionalInterface.h" + +#include "GroupWidget.h" +#include "GroupScene.h" +#include "GroupItem.h" +#include "BoxItem.h" +#include "InterfaceItem.h" +#include "ConnectionItem.h" + +#include "BlockLibraryWidget.h" +#include "BlockLibraryTree.h" + +#include "InterfacePropertiesWindow.h" + + +Dispatcher::Dispatcher(Parameters* _params, MainWindow* _window) { + params = _params; + mainWindow =_window; + params->setDispatcher(this); + currentGroup = NULL; + topGroup = NULL; +} + +GroupWidget *Dispatcher::loadProject(const QString& filename) { + + QDomElement root; + try { + root = params->openProjectFile(filename); + } + catch(Exception err) { + return NULL; + } + + // creating the top widget/scene + topGroup = new GroupWidget(NULL,this,params); + currentGroup = topGroup; + // getting the newly created scene + GroupScene *scene = topGroup->getScene(); + params->setTopScene(scene); + params->setCurrentScene(scene); + + try { + params->loadProject(root); + } + catch(Exception e){ + cerr << qPrintable(e.getDefaultMessage()) << endl; + cerr << "Aborting ..." << endl; + // TO DO : deleteting topGroup and all + return NULL; + } + + return topGroup; +} + +void Dispatcher::closeCurrentProject() { + + foreach(GroupWidget* win, groupList) { + win->deleteLater(); + } + params->destroyGraph(); +} + +bool Dispatcher::connect(InterfaceItem *iface1, InterfaceItem *iface2) { + + ConnectedInterface* ref1 = iface1->refInter; + ConnectedInterface* ref2 = iface2->refInter; + // connect both interface + + bool ok1 = false; + bool ok2 = false; + + if (ref1->canConnectTo(ref2)) { + ok1 = ref1->connectTo(ref2); + ok1 = ok1 & ref2->connectFrom(ref1); + } + if (ref2->canConnectTo(ref1)) { + ok2 = ref2->connectTo(ref1); + ok2 = ok2 & ref1->connectFrom(ref2); + } + if ((ok1 == true) || (ok2 == true)) { + + iface1->getOwner()->getScene()->createConnectionItem(iface1,iface2); + + unselectAllItems(); + params->unsaveModif = true; + return true; + } + return false; +} + +void Dispatcher::checkSelection(){ + InterfaceItem *iface1 = NULL; + InterfaceItem *iface2 = NULL; + + GroupScene *scene = params->getCurrentScene(); + QList list = scene->getGroupAndBlocks(); + foreach(AbstractBoxItem *block, list){ + InterfaceItem *tmp = block->getCurrentInterface(); + if (tmp != NULL) { + if (iface1 == NULL) { + iface1 = tmp; + } + else { + iface2 = tmp; + } + } + } + if(iface1 != NULL && iface2 != NULL){ + connect(iface1,iface2); + } +} + +void Dispatcher::unselectAllItems(int direction){ + + GroupScene *scene = params->getCurrentScene(); + + foreach(BoxItem* block, scene->getBlockItems()) { + block->setSelected(false); + block->setCurrentInterface(NULL); + } + scene->unselecteInterfaces(); + scene->update(); +} + +void Dispatcher::setCurrentGroupWidget(GroupWidget *win){ + win->setFocus(); + win->changeConnectionMode(-1); + currentGroup = win; + params->setCurrentScene(win->getScene()); +} + +void Dispatcher::changeConnectionMode(int mode){ + + /* + foreach(GroupWidget* win, groupList){ + + QToolButton* buttonNewConnection = win->getButtonNewConnection(); + + QPalette pal = buttonNewConnection->palette(); + + if(mode == -1){ + if(params->sceneMode != Parameters::EditOnConnection){ + params->sceneMode = Parameters::EditOnConnection; + pal.setColor(QPalette::Button, QColor(Qt::lightGray)); + } else { + params->sceneMode = Parameters::EditNoOperation; + pal.setColor(QPalette::Button, QColor("#edeceb")); + } + } + else if(mode == Parameters::EditOnConnection){ + params->sceneMode = Parameters::EditOnConnection; + pal.setColor(QPalette::Button, QColor(Qt::lightGray)); + } + else { + params->sceneMode = Parameters::EditNoOperation; + pal.setColor(QPalette::Button, QColor("#edeceb")); + } + unselectAllInterfaces(); + + buttonNewConnection->setAutoFillBackground(true); + buttonNewConnection->setPalette(pal); + buttonNewConnection->update(); + } + */ +} + +void Dispatcher::rename(AbstractBoxItem *item){ + + bool ok; + QString text = QInputDialog::getText(NULL, "Rename an element", + "New name:", QLineEdit::Normal, + item->getRefBlock()->getName(), &ok); + + if(ok){ + if(!text.isEmpty() && text.length() < 30){ + item->getRefBlock()->setName(text); + if(item->isGroupItem()){ + if (currentGroup->isTopGroup()) { + mainWindow->setWindowTitle("blast - "+text); + } + else { + currentGroup->setWindowTitle("blast - "+text); + } + } + } + else { + QMessageBox::warning(NULL,"Error in given name", + "the element name must be shorter than 30 characters and can't be empty!", + QMessageBox::Ok); + rename(item); + } + } +} + +void Dispatcher::rename(InterfaceItem *item){ + bool ok; + QString text = QInputDialog::getText(NULL, "Rename an interface", + "New name:", QLineEdit::Normal, + item->refInter->getName(), &ok); + + /* CAUTION: when renaming an interface item, there are two cases : + - it refers to a functional block interface (fbi): the fbi keeps its name + and the new name is given to item + - it refers to a group block interface (gbi) : both gbi and item store the new name + + */ + if(ok && !text.isEmpty() && text.length() < 30) { + if (item->refInter->getOwner()->isGroupBlock()) { + item->refInter->setName(text); + } + item->setName(text); + } + else { + QMessageBox::warning(NULL,"Error in given name", + "the interface name must be shorter than 30 characters and can't be empty!", + QMessageBox::Ok); + rename(item); + } +} + +void Dispatcher::duplicateBlock(BoxItem *item){ + + GroupScene *scene = params->getCurrentScene(); + AbstractBlock* block = item->getRefBlock(); + AbstractBlock *newBlock; + + // only duplicate functional blocks + if(block->isFunctionalBlock()) { + + // adding to the model + FunctionalBlock* funBlock = (FunctionalBlock*)block; + newBlock = params->duplicateFunctionalBlock(funBlock); + // adding to the view + scene->createBlockItem(newBlock); + + params->unsaveModif = true; + } +} + +void Dispatcher::duplicateInterface(InterfaceItem *item){ + AbstractInterface *refI = item->refInter; + if (! refI->isFunctionalInterface()) return; + + AbstractBlock *refB = refI->getOwner(); + if(! refB->isFunctionalBlock()) return; + + FunctionalInterface* iface = (FunctionalInterface*)refI; + AbstractInterface *otherRef = iface->clone(); + if (otherRef == NULL) { + QMessageBox::warning(NULL,"Error while cloning an interface","the interface cannot be cloned because its maximum multiplicity is reached", QMessageBox::Ok); + return; + } + + refB->addInterface(otherRef); + + InterfaceItem *otherIface = new InterfaceItem(item->getPosition(),item->getOrientation(),(ConnectedInterface*)otherRef,item->getOwner(),params); + item->getOwner()->addInterface(otherIface,true); +} + + +void Dispatcher::addBlock(int idCategory, int idBlock) { + + GroupScene *scene = params->getCurrentScene(); + FunctionalBlock* newOne = params->addFunctionalBlock(idCategory, idBlock); + scene->createBlockItem(newOne); +} + + +GroupWidget *Dispatcher::createTopScene(){ + + // creating the model part of the group + Graph* graph = params->createGraph(); + GroupBlock *refBlock = graph->getTopGroup(); + + // creating a fake and not connected interface + //AbstractInterface* iface = new GroupInterface(refBlock,"grp_iface",AbstractInterface::Input,AbstractInterface::Top); + + // creating the group widget + topGroup = new GroupWidget(NULL,this,params); + currentGroup = topGroup; + // getting the newly created scene + GroupScene *scene = topGroup->getScene(); + params->setTopScene(scene); + params->setCurrentScene(scene); + // creating the view part of the group + GroupItem *group = new GroupItem(NULL,refBlock,this,params); + + // adding the fake interface to the top group item + //InterfaceItem* item = new InterfaceItem(0.0 , Parameters::West, (ConnectedInterface*)iface, group, params); + //group->addInterface(item,true); + + scene->setGroupItem(group); + + return topGroup; +} + +GroupWidget *Dispatcher::createChildScene(GroupWidget* parentWidget, BoxItem *upperItemOfGroupItem) { + + GroupBlock* parentBlock = NULL; + if (upperItemOfGroupItem != NULL) { + parentBlock = AB_TO_GRP(upperItemOfGroupItem->getRefBlock()); + } + // creating the model part of the group + GroupBlock *groupBlock = new GroupBlock(parentBlock); + groupBlock->setName("no name"); + // creating the view part of the group + GroupItem *groupItem = new GroupItem(upperItemOfGroupItem,groupBlock,this,params); + // creating the group widget + GroupWidget* group = new GroupWidget(parentWidget, this, params); + // getting the newly created scene + GroupScene *scene = group->getScene(); + // affecting group item to the scene + scene->setGroupItem(groupItem); + + return group; +} + +void Dispatcher::showRaiseWindow(AbstractBoxItem *item) { + GroupWidget* win = item->getScene()->getGroupWindow(); + if (win->isTopGroup()) { + mainWindow->show(); + mainWindow->raise(); + } + else { + win->show(); + win->raise(); + } + currentGroup = win; + params->setCurrentScene(currentGroup->getScene()); +} + +void Dispatcher::showRstClkInter(AbstractBoxItem *item) { + + item->setRstClkVisible(!item->isRstClkVisible()); + item->resetInterfacesPosition(); + + item->getScene()->updateConnectionItemsShape(); +} + +void Dispatcher::addNewFullGroup() { + +#ifdef DEBUG_INCLFUN + + QList listBlocks = params->getCurrentScene()->getSelectedBlocks(); //selected blocks in the current scene + QList listAbstractBlocks; //abstract blocks in the group + QList connections = params->getCurrentScene()->getConnectionItems(); + + /* What must be done: + 1 - creating a new GroupBlock + 2 - moving the selected blocks from the current GroupBlock to the new GroupBlock + 3 - creating a BlockItem that references the new GroupBlock + 4 - creating a new GroupWidget + 5 - creating a new GroupItem added to the scene of the GroupWidget + + */ + + /* step 1 : creating new GroupBlock that will have as a parent the GroupBlock + associated to the GroupItem of the current scene + */ + GroupBlock* parentBlock = params->getCurrentScene()->getGroupItem()->getRefBlock(); + GroupBlock* newGroupBlock = new GroupBlock(parentBlock); + /* step 2: moving selected blocks */ + foreach(BlockItem* blockItem, listBlocks) { + parentBlock->removeBlock(blockItem->getRefBlock()); + newGroupBlock->addBlock(blockItem->getRefBlock()); + } + + GroupItem *parent = currentGroup->getScene()->getGroupItem(); + GroupBlock *groupBlock = new GroupBlock(((GroupBlock*)parent->getRefBlock()),params->currentWindow); + BlockItem *blockItem = new BlockItem(params->getCurrentScene()->getGroupItem(),groupBlock,this,params); + GroupItem *groupItem = new GroupItem(blockItem,groupBlock,this,params); + + //create the new window + GroupWidget* win = new GroupWidget(this,params); + win->getScene()->setGroupItem(groupItem); + win->getScene()->addItem(groupItem); + ((GroupBlock*)groupItem->getRefBlock())->setWindow(win); + params->addWindow(win); + win->show(); + + //add the new group + params->getCurrentScene()->addBlockItem(blockItem); + params->getCurrentScene()->addItem(blockItem); + ((GroupItem*)params->getCurrentScene()->getGroupItem())->addBlockItem(blockItem); + + //replace selected blocks in the group + foreach(AbstractBoxItem *block, listBlocks){ + ((GroupItem*)block->getParentItem())->removeBlockItem(block); + ((GroupBlock*)block->getParentItem()->getRefBlock())->removeBlock(block->getRefBlock()); + params->getCurrentScene()->removeItem(block); + params->getCurrentScene()->removeBlockItem(block); + + groupBlock->addBlock(block->getRefBlock()); + listAbstractBlocks.append(block->getRefBlock()); + + block->setUpperItem(groupItem); + groupItem->addBlockItem(block); + win->getScene()->addItem(block); + win->getScene()->addBlockItem(block); + } + + //replace connection between selected blocks in the group + foreach(ConnectionItem *conn, connections){ + if(listBlocks.contains(conn->getFromInterfaceItem()->getOwner())){ + if(listBlocks.contains(conn->getToInterfaceItem()->getOwner())){ + parent->removeConnection(conn); + params->getCurrentScene()->removeItem(conn); + + groupItem->addConnection(conn); + win->getScene()->addItem(conn); + } + } + } + + //create new interfaces and connections for the new group + foreach(AbstractBoxItem *block, listBlocks){ + foreach(InterfaceItem *inter, block->getInterfaces()){ + cout << "inter : " << inter->getName().toStdString() << endl; + if(inter->refInter->getConnectedFrom() != NULL && inter->refInter->getDirection() == AbstractInterface::Input){ + cout << "connected from non null" << endl; + if(!listAbstractBlocks.contains(inter->refInter->getConnectedFrom()->getOwner())){ + + AbstractInterface *iface = inter->refInter->clone(0); + iface->setName(iface->getName()+"_group"); + groupBlock->addInterface(iface); + + InterfaceItem *ifaceItem = new InterfaceItem(0,Parameters::East,iface,blockItem,params); + blockItem->addInterface(ifaceItem); + blockItem->resetInterfacesPosition(); + + InterfaceItem *ifaceGroupItem = new InterfaceItem(0,Parameters::West,iface,groupItem,params); + groupItem->addInterface(ifaceGroupItem); + groupItem->resetInterfacesPosition(); + foreach(ConnectionItem* conn, currentGroup->getScene()->getInterfaceConnections(inter)){ + if(conn->getToInterfaceItem() == inter){ + conn->setToInterfaceItem(ifaceItem); + ifaceItem->refInter->setConnectedFrom(NULL); + conn->getFromInterfaceItem()->refInter->clearConnectedTo(); + connect(ifaceItem,conn->getFromInterfaceItem()); + } + } + params->setCurrentWindow(win); + + inter->refInter->setConnectedFrom(NULL); + ifaceGroupItem->refInter->clearConnectedTo(); + connect(inter,ifaceGroupItem); + params->setCurrentWindow(mainWindow); + } + } + + if(!inter->refInter->getConnectedTo().isEmpty() && inter->refInter->getDirection() == AbstractInterface::Output){ + cout << "connected to non null" << endl; + foreach(AbstractInterface *iface, inter->refInter->getConnectedTo()){ + if(!listAbstractBlocks.contains(iface->getOwner())){ + + AbstractInterface *iface = inter->refInter->clone(0); + iface->setName(iface->getName()+"_group"); + groupBlock->addInterface(iface); + + InterfaceItem *ifaceItem = new InterfaceItem(0,Parameters::East,iface,blockItem,params); + blockItem->addInterface(ifaceItem); + blockItem->resetInterfacesPosition(); + + foreach(ConnectionItem* conn, currentGroup->getScene()->getInterfaceConnections(inter)){ + if(conn->getFromInterfaceItem() == inter){ + conn->setFromInterfaceItem(ifaceItem); + iface->addConnectedTo(conn->getToInterfaceItem()->refInter); + conn->getToInterfaceItem()->refInter->setConnectedFrom(iface); + } + } + + InterfaceItem *ifaceGroupItem = new InterfaceItem(0,Parameters::East,iface,groupItem,params); + groupItem->addInterface(ifaceGroupItem); + groupItem->resetInterfacesPosition(); + inter->refInter->clearConnectedTo(); + ifaceGroupItem->refInter->setConnectedFrom(NULL); + connect(ifaceGroupItem,inter); + } + } + } + } + } + + //update window + + parent->updateShape(); + currentGroup->getScene()->updateConnectionItemsShape(); + currentGroup = win; + groupItem->updateShape(); + win->getScene()->updateConnectionItemsShape(); + groupItem->update(groupItem->boundingRect()); + +#endif +} + +void Dispatcher::removeBlock(AbstractBoxItem *item) { + +#ifdef DEBUG_INCLFUN + + GroupScene *scene = params->getCurrentScene(); + AbstractBlock* block = item->getRefBlock(); + if (block->isReferenceBlock()) return; + + GroupBlock* group = (GroupBlock*)item->getParentItem()->getRefBlock(); + + removeConnections(item); + + //récupérer l'objet + group->removeBlock(block); + + //remove the associated window + if(block->isGroupBlock()){ + foreach(QWidget *window, params->windows){ + if(!window->inherits("MainWindow")){ + if(((GroupWidget*)window)->getScene()->getGroupItem()->getRefBlock() == block){ + params->removeWindow(window); + delete window; + } + } + } + } + + delete block; + + //supprimer l'item de la scène + cout << "dispatcher : remove item of scene " << params->currentWindow << endl; + ((GroupItem *)scene->getGroupItem())->removeBlockItem(item); + scene->removeItem(item); + scene->removeBlockItem(item); + delete item; + + ((GroupItem *)scene->getGroupItem())->updateShape(); + + params->updateToolbar(); + params->unsaveModif = true; + +#endif +} + +void Dispatcher::removeAllBlockConnections(AbstractBoxItem *block) { + + GroupScene* scene = block->getScene(); + // supprimer les connections associées au bloc + foreach (ConnectionItem *conn, scene->getConnectionItems()) { + if(conn->getToInterfaceItem()->owner == block || conn->getFromInterfaceItem()->owner == block){ + removeConnection(conn); + } + } + scene->getGroupItem()->updateInterfacesAndConnections(); +} + +void Dispatcher::removeConnection(ConnectionItem *conn) { + + GroupScene *scene = params->getCurrentScene(); + GroupItem* currentGroup = scene->getGroupItem(); + + conn->getFromInterfaceItem()->unconnectTo(conn->getToInterfaceItem()); + + scene->removeConnectionItem(conn); + delete conn; + + currentGroup->updateInterfacesAndConnections(); + params->unsaveModif = true; +} + +void Dispatcher::removeUselessGroupInterfaces() { + + GroupScene *scene = params->getCurrentScene(); + GroupItem* currentGroup = scene->getGroupItem(); + + foreach(InterfaceItem *inter, currentGroup->getInterfaces()) { + if(inter->refInter->getConnectedTo().length() == 0) { + // NB : remove from view also remove from model + currentGroup->removeInterface(inter); + } + } + scene->updateConnectionItemsShape(); +} + +void Dispatcher::showBlocksLibrary(){ + cout << "showing block library" << endl; + mainWindow->getLibrary()->show(); + mainWindow->getLibrary()->raise(); +} + +void Dispatcher::showProperties(InterfaceItem *inter) +{ + new InterfacePropertiesWindow(inter); +} + +/* connectInterToGroup() : + The only way for a block (functional of group) within a GroupItem to be connected + to the latter is to right-click on one of its interfaces and to choose "connect to group". + That action will create a new InterfaceItem on the GroupItem and a connectionItem between the + interfaces. +*/ +void Dispatcher::connectInterToGroup(InterfaceItem *item){ + + // getting the GroupBlock and GroupItem that are parent of the block that owns item + ConnectedInterface *refInter = item->refInter; + GroupBlock* parentBlock = AB_TO_GRP(refInter->getOwner()->getParent()); + GroupItem *parentItem = item->getOwner()->getScene()->getGroupItem(); + + // creating/adding the group interface in the graph model + GroupInterface *groupInter = new GroupInterface(parentBlock,refInter->getName()+"_group",refInter->getDirection(),refInter->getLevel()); + groupInter->setType(refInter->getType()); + groupInter->setWidth(refInter->getWidth()); + groupInter->setPurpose(refInter->getPurpose()); + parentItem->getRefBlock()->addInterface(groupInter); + + // connect both interface + bool ok = true; + if (refInter->getDirection() == AbstractInterface::Output) { + ok = refInter->connectTo(groupInter); + ok = ok & groupInter->connectFrom(refInter); + } + else if (refInter->getDirection() == AbstractInterface::Input) { + ok = groupInter->connectTo(refInter); + ok = ok & refInter->connectFrom(groupInter); + } + else if (refInter->getDirection() == AbstractInterface::InOut) { + ok = refInter->connectTo(groupInter); + ok = ok & groupInter->connectFrom(refInter); + ok = ok & groupInter->connectTo(refInter); + ok = ok & refInter->connectFrom(groupInter); + } + if (!ok) { + cerr << "abnormal case while connecting a block iface to its parent group" << endl; + } + // creating/adding the group interface in the current scene model, and connection item + InterfaceItem *groupIfaceItem = new InterfaceItem(0,item->getOrientation(),groupInter,parentItem,params); + parentItem->addInterface(groupIfaceItem,true); + + parentItem->getScene()->createConnectionItem(item, groupIfaceItem); + + // if groupItem is not topGroup, must also add a new interface to the parent BlockItem + BoxItem* parent2Item = parentItem->getParentItem(); + if(parent2Item != NULL){ + InterfaceItem *blockIfaceItem = new InterfaceItem(0,item->getOrientation(),groupInter,parent2Item,params); + parent2Item->addInterface(blockIfaceItem,true); + } + + + parentItem->getScene()->updateConnectionItemsShape(); + unselectAllItems(); + params->unsaveModif = true; + + +} + +void Dispatcher::disconnectInterFromGroup(InterfaceItem *item) { + static QString fctName = "Dispatcher::disconnectInterFromGroup()"; +#ifdef DEBUG_FCTNAME + cout << "call to " << qPrintable(fctName) << endl; +#endif + + // getting the GroupBlock and GroupItem that are parent of the block that owns item + ConnectedInterface *refInter = item->refInter; + ConnectedInterface *groupInter = NULL; + GroupBlock* parentGroup = AB_TO_GRP(refInter->getOwner()->getParent()); + GroupItem *parentItem = item->getOwner()->getScene()->getGroupItem(); + + // removing the connection from graph +#ifdef DEBUG + cout << "removing connections from graph ..." ; +#endif + + if (refInter->getDirection() == AbstractInterface::Output) { + groupInter = refInter->getConnectionToParentGroup(); // must be a single connection to + refInter->clearConnectedTo(); + groupInter->clearConnectedFrom(); + } + else if (refInter->getDirection() == AbstractInterface::Input) { + groupInter = refInter->getConnectedFrom(); + refInter->clearConnectedFrom(); + groupInter->clearConnectedTo(); + } + else if (refInter->getDirection() == AbstractInterface::InOut) { + groupInter = refInter->getConnectionToParentGroup(); // must be a single connection to + refInter->clearConnectedTo(); + refInter->clearConnectedFrom(); + groupInter->clearConnectedTo(); + groupInter->clearConnectedFrom(); + } +#ifdef DEBUG + cout << "done." << endl ; +#endif + + if (groupInter == NULL) { + cerr << "abnormal case 1 while removing an interface item of a block, linked to a parent group" << endl; + } + +#ifdef DEBUG + cout << "getting group interface item, and connection item ..." ; +#endif + + + InterfaceItem* groupIfaceItem = parentItem->searchInterfaceByRef(groupInter); + if (groupIfaceItem == NULL) { + cerr << "abnormal case 2 while removing an interface item of a block, linked to a parent group" << endl; + } + ConnectionItem* conn = parentItem->getScene()->searchConnectionItem(item,groupIfaceItem); + if (conn == NULL) { + cerr << "abnormal case 3 while removing an interface item of a block, linked to a parent group" << endl; + } +#ifdef DEBUG + cout << "done." << endl ; +#endif + + // removing the interface group item from the group item, and the connection item +#ifdef DEBUG + cout << "removing group interface item, and connection item ..." ; +#endif + + item->removeConnectionItem(conn); + groupIfaceItem->removeConnectionItem(conn); + parentItem->removeInterface(groupIfaceItem); // CAUTION : this deletes the interface item. + parentItem->getScene()->removeConnectionItem(conn); +#ifdef DEBUG + cout << "done." << endl ; +#endif + + // removing the interface box item in the parent scene +#ifdef DEBUG + cout << "removing the inteeface item of box item in parent scene if needed ..." ; +#endif + + BoxItem* parent2Item = parentItem->getParentItem(); + if (parent2Item != NULL) { + InterfaceItem* group2IfaceItem = parent2Item->searchInterfaceByRef(groupInter); + parent2Item->removeInterface(group2IfaceItem); + } +#ifdef DEBUG + cout << "done." << endl ; +#endif + + // removing the interface group from the group +#ifdef DEBUG + cout << "removing group interface ..." ; +#endif + parentGroup->removeInterface(groupInter); +#ifdef DEBUG + cout << "done." << endl ; +#endif + +} + +void Dispatcher::removeGroupInterface(InterfaceItem *item) { + static QString fctName = "Dispatcher::removeGroupInterface()"; +#ifdef DEBUG_FCTNAME + cout << "call to " << qPrintable(fctName) << endl; +#endif + + /* NB: there is a single connection between item and another one that is owned + by a BoxItem. Thus, we just have to find it and to call disconnectInterFromGroup(); + */ + ConnectionItem* conn = item->connections.at(0); + if (conn->getFromInterfaceItem() == item) { + disconnectInterFromGroup(conn->getToInterfaceItem()); + } + else { + disconnectInterFromGroup(conn->getFromInterfaceItem()); + } +} + +GroupScene* Dispatcher::searchSceneById(int id) { + foreach(GroupWidget *group, groupList){ + if(group->getScene()->getId() == id) + return group->getScene(); + } + cout << "search scene by id :" << id << " :: not found..." << endl; + return NULL; +} + +GroupItem *Dispatcher::searchGroupItemById(int id) { + foreach(GroupWidget *group, groupList) { + GroupScene* scene = group->getScene(); + if (scene->getGroupItem()->getId() == id) return scene->getGroupItem(); + } + cout << "search GroupItem by id :" << id << " :: not found..." << endl; + return NULL; +} + +BoxItem *Dispatcher::searchBlockItemById(int id) { + foreach(GroupWidget *group, groupList) { + + GroupScene* scene = group->getScene(); + foreach(BoxItem *item, scene->getBlockItems()){ + if(item->getId() == id){ + return item; + } + } + } + cout << "search BlockItem by id :" << id << " :: not found..." << endl; + return NULL; +} + +InterfaceItem* Dispatcher::searchInterfaceItemById(int id) { + + foreach(GroupWidget *group, groupList) { + + GroupScene* scene = group->getScene(); + + foreach(InterfaceItem *item, scene->getGroupItem()->getInterfaces()){ + if(item->getId() == id){ + return item; + } + } + foreach(BoxItem *block, scene->getBlockItems()){ + foreach(InterfaceItem *item, block->getInterfaces()){ + if(item->getId() == id){ + return item; + } + } + } + } + cout << "search interface by id :" << id << " :: not found..." << endl; + return NULL; +} + diff --git a/Dispatcher.h b/Dispatcher.h new file mode 100644 index 0000000..4475518 --- /dev/null +++ b/Dispatcher.h @@ -0,0 +1,89 @@ +#ifndef __DISPATCHER_H__ +#define __DISPATCHER_H__ + +#include + +#include +#include +#include + +class Graph; +class Parameters; +class MainWindow; +class GroupWidget; +class GroupScene; +class AbstractBoxItem; +class GroupItem; +class BoxItem; +class ConnectionItem; +class InterfaceItem; + + + +using namespace std; +using namespace Qt; + +class Dispatcher { + +public: + Dispatcher(Parameters* _params, + MainWindow* _window); + + GroupWidget* loadProject(const QString& filename); + + inline int getNumberOfScenes() { return groupList.length(); } + bool connect(InterfaceItem *iface1, InterfaceItem *iface2); + void checkSelection(); + void unselectAllItems(int direction=0); + void setCurrentGroupWidget(GroupWidget *win); + void changeConnectionMode(int mode = -1); + void rename(AbstractBoxItem* item); + void rename(InterfaceItem* item); + GroupWidget* createTopScene(); + GroupWidget* createChildScene(GroupWidget* parentWidget, BoxItem* upperItemOfGroupItem = NULL); + void showRaiseWindow(AbstractBoxItem *item); + void showRstClkInter(AbstractBoxItem *item); + void addNewFullGroup(); + + inline GroupWidget* getCurrentGroup() { return currentGroup; } + + bool isCurrentProject; + +public slots: + + GroupScene* searchSceneById(int id); + BoxItem* searchBlockItemById(int id); + GroupItem* searchGroupItemById(int id); + InterfaceItem* searchInterfaceItemById(int id); + + void removeBlock(AbstractBoxItem* item); + void duplicateBlock(BoxItem* item); + void duplicateInterface(InterfaceItem* item); + void addBlock(int idCategory, int idBlock); + ConnectionItem *addConnection(InterfaceItem *input, InterfaceItem *output); + void removeAllBlockConnections(AbstractBoxItem *block); + void removeConnection(ConnectionItem *conn); + void removeUselessGroupInterfaces(); + void showBlocksLibrary(); + void showProperties(InterfaceItem *inter); + void connectInterToGroup(InterfaceItem* item); + void disconnectInterFromGroup(InterfaceItem* item); + void removeGroupInterface(InterfaceItem* item); + + void addConnection(); + + void closeCurrentProject(); + +private: + + // the model + Parameters* params; + + // attributes that corresponds to the views + MainWindow* mainWindow; + QList groupList; + GroupWidget* currentGroup; + GroupWidget *topGroup; +}; + +#endif // __DISPATCHER_H__ diff --git a/Exception.cpp b/Exception.cpp new file mode 100644 index 0000000..c45c367 --- /dev/null +++ b/Exception.cpp @@ -0,0 +1,39 @@ +#include "Exception.h" + +Exception::Exception(int _id) { + id = _id; + message = getDefaultMessage(); +} + + +Exception::Exception(const Exception& other) { + id = other.id; + message = other.message; +} + +QString Exception::getDefaultMessage() { + QString ret=""; + switch(id) { + case CONFIGFILE_CORRUPTED : ret = tr("Blast configuration file is corrupted"); break; + case CONFIGFILE_NOACCESS : ret = tr("Blast configuration file cannot be read"); break; + case PROJECTFILE_CORRUPTED : ret = tr("Project file is corrupted"); break; + case PROJECTFILE_NOACCESS : ret = tr("Project file cannot be read"); break; + case BLOCKPATH_NOACCESS : ret = tr("Directory containing references cannot be accessed (no rights/existence)"); break; + case IMPLPATH_NOACCESS : ret = tr("Directory containing implementations cannot be accessed (no rights/existence)"); break; + case BLOCKFILE_CORRUPTED : ret = tr("Block file is corrupted"); break; + case BLOCKFILE_NOACCESS : ret = tr("Block file cannot be read"); break; + case IMPLFILE_CORRUPTED : ret = tr("Implementation file is corrupted"); break; + case IMPLFILE_NOACCESS : ret = tr("Implementation file cannot be read"); break; + case BLOCK_NULL : ret = tr("A parameter of type AbstractBlock* has been provided with NULL value."); break; + case BLOCK_INVALID_TYPE : ret = tr("A parameter of type AbstractBlock* is used with an incorrect instance type."); break; + case IFACE_NULL : ret = tr("A parameter of type AbstractInterface* has been provided with NULL value."); break; + case IFACE_INVALID_TYPE : ret = tr("A parameter of type AbstractInterface* is used with an incorrect instance type."); break; + case IFACE_MULTIPLICITY_REACHED : ret = tr("Impossible to create another instance of a GraphInterface: the maximum multiplicity is reached."); break; + case BLOCKITEM_NULL : ret = tr("A parameter of type AbstractBlockItem* has been provided with NULL value."); break; + case BLOCKITEM_INVALID_TYPE : ret = tr("A parameter of type AbstractBlockItem* is used with an incorrect instance type."); break; + case WIDTHS_NOT_EQUALS : ret = tr("Two interfaces are connected but don't have the same widths."); break; + case INVALID_VALUE : ret = tr("parameter value is not correct (e.g. not numeric, invalid other parameter name, ...)."); break; + } + + return ret; +} diff --git a/Exception.h b/Exception.h new file mode 100644 index 0000000..32b2e49 --- /dev/null +++ b/Exception.h @@ -0,0 +1,80 @@ +/*-==============================================================- + +file : Exception.h + +creation date : 08/04/2015 + +author : S. Domas (sdomas@iut-bm.univ-fcomte.fr) + +description : + +supp. infos : saved in UTF-8 [éè] + +-==============================================================-*/ +#ifndef __EXCEPTION_H__ +#define __EXCEPTION_H__ + +#include +#include + +#include + +// exceptions for file accesses +#define CONFIGFILE_NOACCESS 1 +#define CONFIGFILE_CORRUPTED 2 + +#define PROJECTFILE_NOACCESS 3 +#define PROJECTFILE_CORRUPTED 4 + +#define BLOCKPATH_NOACCESS 5 +#define IMPLPATH_NOACCESS 6 + +#define BLOCKFILE_NOACCESS 7 +#define BLOCKFILE_CORRUPTED 8 + +#define IMPLFILE_NOACCESS 9 +#define IMPLFILE_CORRUPTED 10 + +#define VHDLFILE_NOACCESS 11 + +// exceptions for block manipulations +#define BLOCK_NULL 100 +#define BLOCK_INVALID_TYPE 101 + +// exceptions for interfaces manipulations +#define IFACE_NULL 200 +#define IFACE_INVALID_TYPE 201 +#define IFACE_MULTIPLICITY_REACHED 202 + +// exceptions for block items manipulations +#define BLOCKITEM_NULL 300 +#define BLOCKITEM_INVALID_TYPE 301 + +// exceptions for width interfaces validation +#define WIDTHS_NOT_EQUALS 400 + +// exceptions for VHDL generation +#define INVALID_VALUE 500 + +using namespace std; +using namespace Qt; + +class Exception : public QObject { + +public: + + Exception(int _id); + Exception(const Exception& other); + + inline int getType() { return id; } + inline void setMessage(QString _message) { message = _message; } + inline QString getMessage() { return message; } + QString getDefaultMessage(); + +private: + int id; + QString message; + +}; + +#endif //__EXCEPTION_H__ diff --git a/FunctionalBlock.cpp b/FunctionalBlock.cpp new file mode 100644 index 0000000..f547084 --- /dev/null +++ b/FunctionalBlock.cpp @@ -0,0 +1,78 @@ +#include "FunctionalBlock.h" +#include "ReferenceBlock.h" +#include "GroupBlock.h" +#include "AbstractInterface.h" +#include "FunctionalInterface.h" +#include "ReferenceInterface.h" +#include "BlockParameter.h" + + +FunctionalBlock::FunctionalBlock(GroupBlock *_parent, ReferenceBlock *_reference) throw(Exception) : AbstractBlock() { + //if (! _reference->isReferenceBlock()) throw(Exception(BLOCK_INVALID_TYPE)); + //if (! _group->isGroupBlock()) throw(Exception(BLOCK_INVALID_TYPE)); + reference = _reference; + parent = _parent; + name = reference->getName(); +} + + +void FunctionalBlock::parametersValidation(QList* checkedBlocks, QList *blocksToConfigure) { + /* + checkedBlocks->append(this); + + foreach(BlockParameter* param, params){ + if(param->isUserParameter() && !param->isValueSet()){ + if(!blocksToConfigure->contains(param->getOwner())){ + blocksToConfigure->append(param->getOwner()); + } + } + } + foreach(AbstractInterface *inter, outputs){ + foreach(AbstractInterface *connectedInter, inter->getConnectedTo()){ + if(!checkedBlocks->contains(connectedInter->getOwner())){ + connectedInter->getOwner()->parametersValidation(checkedBlocks, blocksToConfigure); + } + } + } + */ +} + +bool FunctionalBlock::isFunctionalBlock() { + return true; +} + +void FunctionalBlock::populate() { + int i; + BlockParameter* p; + AbstractInterface* inter; + + QList lstParam = reference->getParameters(); + for(i=0;iclone(); + addParameter(p); + } + + QList lstInter = reference->getInterfaces(); + for(i=0;igetXmlFile(); +} + +QString FunctionalBlock::getReferenceHashMd5() +{ + return ((ReferenceBlock *)reference)->getHashMd5(); +} diff --git a/FunctionalBlock.h b/FunctionalBlock.h new file mode 100644 index 0000000..1360244 --- /dev/null +++ b/FunctionalBlock.h @@ -0,0 +1,49 @@ +#ifndef __FUNCTIONALBLOCK_H__ +#define __FUNCTIONALBLOCK_H__ + +#include + +#include + +#include "AbstractBlock.h" +class AbstractBlock; +class ReferenceBlock; +class GroupBlock; +#include "Exception.h" +class Exception; + + +using namespace std; +using namespace Qt; + +/* NOTES : + - NEVER forget to call populate() after creating an instance of GraphBlock. + */ + +class FunctionalBlock : public AbstractBlock { +public: + + FunctionalBlock(GroupBlock* _parent, ReferenceBlock* _reference) throw(Exception); + + // getters + inline ReferenceBlock* getReference() { return reference; } + + // setters + + // testers + bool isFunctionalBlock(); + + // others + + void populate(); // create parameters and interface from reference block + void parametersValidation(QList *checkedBlocks, QList* blocksToConfigure); + + QString getReferenceXmlFile(); + QString getReferenceHashMd5(); + +private: + ReferenceBlock* reference; + +}; + +#endif // __FUNCTIONALBLOCK_H__ diff --git a/FunctionalInterface.cpp b/FunctionalInterface.cpp new file mode 100644 index 0000000..cc9f765 --- /dev/null +++ b/FunctionalInterface.cpp @@ -0,0 +1,197 @@ +#include "ArithmeticEvaluator.h" +#include "FunctionalInterface.h" +#include "ReferenceInterface.h" +#include "GroupInterface.h" +#include "FunctionalBlock.h" +#include "GroupBlock.h" + + +FunctionalInterface::FunctionalInterface(AbstractBlock* _owner, ReferenceInterface *_reference) throw(Exception) : ConnectedInterface(_owner) { + + if (_owner == NULL) throw(Exception(BLOCK_NULL)); + if (_reference == NULL) throw(Exception(IFACE_NULL)); + + if (! _owner->isFunctionalBlock()) throw(Exception(BLOCK_INVALID_TYPE)); + if (! _reference->isReferenceInterface()) throw(Exception(IFACE_INVALID_TYPE)); + + reference = _reference; + + name = reference->getName(); + width = reference->getWidth(); + direction = reference->getDirection(); + purpose = reference->getPurpose(); + level = reference->getLevel(); + connectedFrom = NULL; +} + +bool FunctionalInterface::isFunctionalInterface() { + return true; +} + +int FunctionalInterface::getInterfaceMultiplicity() { + + int i=0; + int ifaceCount = 0; + FunctionalInterface* iface = NULL; + + if (direction == AbstractInterface::Input) { + QList inputs = owner->getInputs(); + for(i=0;igetReference() == reference) { + ifaceCount += 1; + } + } + } + else if (direction == AbstractInterface::Output) { + QList outputs = owner->getOutputs(); + for(i=0;igetReference() == reference) { + ifaceCount += 1; + } + } + } + else if (direction == AbstractInterface::InOut) { + QList bidirs = owner->getBidirs(); + for(i=0;igetReference() == reference) { + ifaceCount += 1; + } + } + } + if (ifaceCount == 0) { + return -1; + } + else if ( reference->getMultiplicity() == -1) { + return ifaceCount+1; + } + else if ( reference->getMultiplicity() > ifaceCount) { + return ifaceCount+1; + } + return -1; +} + +AbstractInterface *FunctionalInterface::clone() { + int id = getInterfaceMultiplicity(); + if (id < 0) return NULL; + FunctionalInterface *inter = new FunctionalInterface(owner, reference); + inter->setWidth(width); + inter->setDirection(direction); + inter->setPurpose(purpose); + inter->setLevel(level); + inter->connectFrom(NULL); + inter->setName(reference->getName()+"_"+QString::number(id)); + return inter; +} + +bool FunctionalInterface::canConnectTo(AbstractInterface *iface) { + + /* NOTE : + necessary conditions : + - this is an output or in/out interface + - iface type must be functional or group interface + - iface->connectedFrom must be NULL + + valid cases: + 1 - iface is owned by a block (group or func) that is within the same group as the block that own this + 1.1 - this is output and iface is input + 1.2 - both are inout + 2 - iface is owned by the parent group of the block that owns this + 2.1 - this is an output, iface is an output of the group + 2.2 - both are inout + + */ + if (direction == Input) return false; + if (iface->isReferenceInterface()) return false; + if (iface->getConnectedFrom() != NULL) return false; + + if (getOwner()->getParent() == iface->getOwner()->getParent()) { + + if ((direction == Output) && (iface->getDirection() == Input)) return true; + if ((direction == InOut) && (iface->getDirection() == InOut)) return true; + } + else if (getOwner()->getParent() == iface->getOwner()) { + if ((direction == Output) && (iface->getDirection() == Output)) return true; + if ((direction == InOut) && (iface->getDirection() == InOut)) return true; + } + + return false; + +} + +bool FunctionalInterface::canConnectFrom(AbstractInterface *iface) { + + /* NOTE : + necessary conditions : + - this is an input or in/out interface + - iface type must be functional or group interface + - this connectedFrom must be NULL + + valid cases: + 1 - iface is owned by a block (group or func) that is within the same group as the block that own this + 1.1 - this is input and iface is output + 1.2 - both are inout + 2 - iface is owned by the parent group of the block that owns this + 2.1 - this is an input, iface is an input of the group + 2.2 - both are inout + */ + if (direction == Output) return false; + if (iface->isReferenceInterface()) return false; + if (connectedFrom != NULL) return false; + + if (getOwner()->getParent() == iface->getOwner()->getParent()) { + + if ((direction == Input) && (iface->getDirection() == Output)) return true; + if ((direction == InOut) && (iface->getDirection() == InOut)) return true; + } + else if (getOwner()->getParent() == iface->getOwner()) { + if ((direction == Input) && (iface->getDirection() == Input)) return true; + if ((direction == InOut) && (iface->getDirection() == InOut)) return true; + } + + return false; +} + + +void FunctionalInterface::connectionsValidation(QStack *interfacetoValidate, QList *validatedInterfaces) throw(Exception) { + + /* + //inputs interfaces + double widthInput, widthOutput; + if(getDirection() == AbstractInterface::Input){ + widthInput = getDoubleWidth(); + widthOutput = getConnectedFrom()->getDoubleWidth(); + if(widthInput != widthOutput){ + throw new Exception(WIDTHS_NOT_EQUALS); + } + foreach(AbstractInterface *inter, getOwner()->getOutputs()){ + if(inter->isConnectedTo()){ + if((!interfacetoValidate->contains(inter)) && (!validatedInterfaces->contains(inter))){ + interfacetoValidate->push(inter); + } + } + } + } + //outputs interfaces + else if(getDirection() == AbstractInterface::Output){ + widthOutput = getDoubleWidth(); + foreach(AbstractInterface *inter, getConnectedTo()){ + widthInput = inter->getDoubleWidth(); + if(widthInput != widthOutput){ + throw new Exception(WIDTHS_NOT_EQUALS); + } + } + foreach(AbstractInterface *inter, getConnectedTo()){ + if((!interfacetoValidate->contains(inter)) && (!validatedInterfaces->contains(inter))){ + interfacetoValidate->push(inter); + } + } + } + else if(getDirection() == AbstractInterface::InOut){ + + } + + */ +} diff --git a/FunctionalInterface.h b/FunctionalInterface.h new file mode 100644 index 0000000..1dfa726 --- /dev/null +++ b/FunctionalInterface.h @@ -0,0 +1,59 @@ +#ifndef __FUNCTIONALINTERFACE_H__ +#define __FUNCTIONALINTERFACE_H__ + +#include + +#include +#include + +#include "ConnectedInterface.h" +class ReferenceInterface; + +#include "Exception.h" + +using namespace std; +using namespace Qt; + + +/* NOTES : + + - A FunctionalInterface instance can be obtained by: + - cloning an existing ReferenceInterface when a new functionalBlock is created by cloning a ReferenceBlock + - cloning an existing FunctionalInterface when its reference has a multiplicity > 1 + + - For an Input, the list connectedFrom can contain ONLY ONE element + - For an Output, the list connectedTo can contain several element + - If connectedTo contains something, then connectedFrom is NULL + - If connectedFrom contains something, the connectedTo is empty. + */ + +class FunctionalInterface : public ConnectedInterface { + +public : + FunctionalInterface(); + FunctionalInterface(AbstractBlock* _owner, ReferenceInterface* _reference) throw(Exception); // create a default interface (see AbstractInterface) + + // getters + inline ReferenceInterface* getReference() { return reference; } + + // setters + + // testers + bool isFunctionalInterface(); + bool canConnectTo(AbstractInterface* iface); + bool canConnectFrom(AbstractInterface* iface); + + // others + + AbstractInterface* clone(); + + void connectionsValidation(QStack *interfacetoValidate, QList *validatedInterfaces) throw(Exception); + int getInterfaceMultiplicity(); + +private: + + ReferenceInterface* reference; + +}; + +#endif // __FUNCTIONALINTERFACE_H__ diff --git a/Graph.cpp b/Graph.cpp new file mode 100644 index 0000000..1c87123 --- /dev/null +++ b/Graph.cpp @@ -0,0 +1,31 @@ +#include "Graph.h" +#include "GroupBlock.h" +#include "ReferenceBlock.h" +#include "FunctionalBlock.h" + +Graph::Graph() { + topGroup = new GroupBlock(NULL); + topGroup->setName("top group"); +} + +Graph::~Graph() { + delete topGroup; +} + +QList Graph::getOutsideInterfaces() { + return topGroup->getInterfaces(); +} + +GroupBlock* Graph::createChildBlock(GroupBlock* parent) { + GroupBlock* b = new GroupBlock(parent); + return b; +} + +FunctionalBlock* Graph::addFunctionalBlock(GroupBlock* group, ReferenceBlock* ref) { + + FunctionalBlock* newBlock = new FunctionalBlock(group,ref); + newBlock->populate(); + group->addBlock(newBlock); + + return newBlock; +} diff --git a/Graph.h b/Graph.h new file mode 100644 index 0000000..7f34682 --- /dev/null +++ b/Graph.h @@ -0,0 +1,35 @@ +#ifndef __GRAPH_H__ +#define __GRAPH_H__ + +#include + +#include +#include + +class GroupBlock; +class ReferenceBlock; +class FunctionalBlock; +class AbstractInterface; + +using namespace std; +using namespace Qt; + + +class Graph { + +public: + Graph(); + ~Graph(); + + QList getOutsideInterfaces(); + inline GroupBlock* getTopGroup() { return topGroup; } + + GroupBlock* createChildBlock(GroupBlock* parent); + FunctionalBlock* addFunctionalBlock(GroupBlock *group, ReferenceBlock *ref); + +private: + GroupBlock* topGroup; + +}; + +#endif // __GRAPH_H__ diff --git a/GroupBlock.cpp b/GroupBlock.cpp new file mode 100644 index 0000000..4ec7f6b --- /dev/null +++ b/GroupBlock.cpp @@ -0,0 +1,78 @@ +#include "GroupBlock.h" +#include "BlockParameterGeneric.h" +#include "AbstractInterface.h" +#include "string.h" +#include + +int GroupBlock::counter = 1; + +GroupBlock::GroupBlock(GroupBlock *_parent) throw(Exception) : AbstractBlock() { + + // force topGroup to false if this group has a parent + if (_parent != NULL) { + topGroup = false; + name = QString("sub_group")+"_"+QString::number(counter++); + } + else { + topGroup = true; + name = QString("top_group"); + } + parent = _parent; + if (parent != NULL) { + // adding this to the child blocks of parent + AB_TO_GRP(parent)->addBlock(this); + } +} + +GroupBlock::~GroupBlock() { + foreach(AbstractBlock* block, blocks) { + delete block; + } +} + +bool GroupBlock::isGroupBlock() { + return true; +} + +void GroupBlock::setParent(AbstractBlock *_parent) { + parent = _parent; + if (parent != NULL) { + topGroup = false; + } +} + +void GroupBlock::removeBlock(AbstractBlock* block) { + blocks.removeAll(block); +} + +void GroupBlock::parametersValidation(QList *checkedBlocks, QList *blocksToConfigure) { + + /* + checkedBlocks->append(this); + + foreach(BlockParameter* param, params){ + if(param->isUserParameter() && !param->isValueSet()){ + if(!blocksToConfigure->contains(param->getOwner())){ + blocksToConfigure->append(param->getOwner()); + } + } + } + foreach(AbstractInterface *inter, outputs){ + foreach(AbstractInterface *connectedInter, inter->getConnectedTo()){ + if(!checkedBlocks->contains(connectedInter->getOwner())){ + connectedInter->getOwner()->parametersValidation(checkedBlocks, blocksToConfigure); + } + } + } + */ +} + +void GroupBlock::addGenericParameter(QString name, QString type, QString value) { + BlockParameter* param = new BlockParameterGeneric(this, name, type, value); + params.append(param); +} + +void GroupBlock::removeGenericParameter(QString name) { + BlockParameter* p = getParameterFromName(name); + if (p != NULL) params.removeAll(p); +} diff --git a/GroupBlock.h b/GroupBlock.h new file mode 100644 index 0000000..3316637 --- /dev/null +++ b/GroupBlock.h @@ -0,0 +1,47 @@ +#ifndef __GROUPBLOCK_H__ +#define __GROUPBLOCK_H__ + +#include + +#include + +#include "AbstractBlock.h" +class AbstractBlock; +#include "Exception.h" +class Exception; + +using namespace std; +using namespace Qt; + + +class GroupBlock : public AbstractBlock { +public: + + GroupBlock(GroupBlock* _parent) throw(Exception); + virtual ~GroupBlock(); + + // getters + + // setters + void setParent(AbstractBlock *_parent); + + // testers + bool isGroupBlock(); + inline bool isTop() { return topGroup; } + + // others + inline void addBlock(AbstractBlock* block) { blocks.append(block); } + void removeBlock(AbstractBlock* block); + void parametersValidation(QList *checkedBlocks, QList* blocksToConfigure); + void addGenericParameter(QString name, QString type, QString value); + void removeGenericParameter(QString name); + // public attributes + static int counter; + +private: + bool topGroup; + QList blocks; // contains instances of FunctionalBlock or GroupBlock + +}; + +#endif // __GROUPBLOCK_H__ diff --git a/GroupInterface.cpp b/GroupInterface.cpp new file mode 100644 index 0000000..14f8b43 --- /dev/null +++ b/GroupInterface.cpp @@ -0,0 +1,116 @@ +#include "GroupInterface.h" +#include "FunctionalInterface.h" +#include "GroupBlock.h" + +GroupInterface::GroupInterface(AbstractBlock* _owner, const QString& _name, int _direction, int _level) throw(Exception) : ConnectedInterface(_owner,_name,"expression","",_direction,AbstractInterface::Data,_level) { + if (! _owner->isGroupBlock()) throw(Exception(BLOCK_INVALID_TYPE)); + + /* If the owner group is the top group, then all its interfaces are at top level => force them to be top. + If not, force them to be basic + */ + if (((GroupBlock*)_owner)->isTop()) { + level = AbstractInterface::Top; + } + else { + level = AbstractInterface::Basic; + } + connectedFrom = NULL; +} + +bool GroupInterface::isGroupInterface() { + return true; +} + +AbstractInterface *GroupInterface::clone() { + GroupInterface *inter = new GroupInterface(owner,name,direction,level); + inter->setWidth(width); + inter->setDirection(direction); + inter->setPurpose(purpose); + inter->setLevel(level); + inter->connectFrom(NULL); + + return inter; +} + + +bool GroupInterface::canConnectTo(AbstractInterface *iface) { + + /* NOTE : + necessary conditions : + - iface type must be functional or group interface + - iface->connectedFrom must be NULL + + valid cases: + 1 - this is owned by the parent group of the block (group or func) that owns iface + 1.1 - this is input and iface is input + 1.2 - both are inout + 2 - this is owned by a group that has the same parent as the block (group or func) that owns iface + 2.1 - this is an output, iface is an input of the group + 2.2 - both are inout + 3 - this is owned by a group and iface by its parent group + 2.1 - this is an output, iface is an output of the group + 2.2 - both are inout + + + */ + if (iface->isReferenceInterface()) return false; + if (iface->getConnectedFrom() != NULL) return false; + + if (this->getOwner() == iface->getOwner()->getParent()) { + if ((direction == Input) && (iface->getDirection() == Input)) return true; + if ((direction == InOut) && (iface->getDirection() == InOut)) return true; + + } + else if (this->getOwner()->getParent() == iface->getOwner()->getParent()) { + if ((direction == Output) && (iface->getDirection() == Input)) return true; + if ((direction == InOut) && (iface->getDirection() == InOut)) return true; + } + else if (this->getOwner()->getParent() == iface->getOwner()) { + if ((direction == Output) && (iface->getDirection() == Output)) return true; + if ((direction == InOut) && (iface->getDirection() == InOut)) return true; + } + + return false; +} + +bool GroupInterface::canConnectFrom(AbstractInterface *iface) { + + /* NOTE : + necessary conditions : + - iface type must be functional or group interface + - this->connectedFrom must be NULL + + valid cases: + 1 - this is owned by the parent group of the block (group or func) that owns iface + 1.1 - this is output and iface is output + 1.2 - both are inout + 2 - this is owned by a group that has the same parent as the block (group or func) that owns iface + 2.1 - this is an input, iface is an output of the group + 2.2 - both are inout + 3 - this is owned by a group and iface by its parent group + 2.1 - this is an input, iface is an input of the group + 2.2 - both are inout + */ + if (iface->isReferenceInterface()) return false; + if (getConnectedFrom() != NULL) return false; + + if (this->getOwner() == iface->getOwner()->getParent()) { + if ((direction == Output) && (iface->getDirection() == Output)) return true; + if ((direction == InOut) && (iface->getDirection() == InOut)) return true; + + } + else if (this->getOwner()->getParent() == iface->getOwner()->getParent()) { + if ((direction == Input) && (iface->getDirection() == Output)) return true; + if ((direction == InOut) && (iface->getDirection() == InOut)) return true; + } + else if (this->getOwner()->getParent() == iface->getOwner()) { + if ((direction == Input) && (iface->getDirection() == Input)) return true; + if ((direction == InOut) && (iface->getDirection() == InOut)) return true; + } + + return false; +} + +void GroupInterface::connectionsValidation(QStack *interfacetoValidate, QList *validatedInterfaces) throw(Exception) { + cout << "group interface connection validation" << endl; +} diff --git a/GroupInterface.h b/GroupInterface.h new file mode 100644 index 0000000..2a22dc7 --- /dev/null +++ b/GroupInterface.h @@ -0,0 +1,54 @@ +#ifndef __GROUPINTERFACE_H__ +#define __GROUPINTERFACE_H__ + +#include + +#include +#include + +#include "ConnectedInterface.h" +#include "Exception.h" +class Exception; + +using namespace std; +using namespace Qt; + +/* NOTES : + + - A GroupInterface instance can be obtained by linking it to an interface of an inner block, via a + contextual menu that appears when right clicking on the inner interface item. + Thus, it is impossible to create a "bypass": an input group interface, directly linked to an output + group interface. + - the direction (in/out/bi) of this interface is the same as that of the inner interface. + - except for the top group, a correct design will always have group interfaces that have + both connectedTo and connectedFrom containings something. Indeed, a group interface must be seen + as a tunnel to communicate data from outside the group to blocks within the group. + Thus, an input group interface has connectedFrom refering to an output interface of a block outside the group + and connectedTo listing input interfaces of blocks within the group. + An output group interface has connectedFrom refering to an output interface of a bock within the group, + and connectedTo listing input interface of blocks outside the group. + + */ + + +class GroupInterface : public ConnectedInterface { + +public : + GroupInterface(AbstractBlock* _owner, const QString& _name, int _direction, int _level = AbstractInterface::Basic) throw (Exception); + + // getters + + // setters + + // testers + bool isGroupInterface(); + bool canConnectTo(AbstractInterface* iface); + bool canConnectFrom(AbstractInterface* iface); + + // others + AbstractInterface *clone(); + void connectionsValidation(QStack *interfacetoValidate, QList *validatedInterfaces) throw(Exception); + +}; + +#endif // __GROUPINTERFACE_H__ diff --git a/GroupItem.cpp b/GroupItem.cpp new file mode 100644 index 0000000..04d7bca --- /dev/null +++ b/GroupItem.cpp @@ -0,0 +1,581 @@ +#include "GroupItem.h" + +#include "ConnectionItem.h" +#include "InterfaceItem.h" +#include "Dispatcher.h" +#include "Parameters.h" +#include "BoxItem.h" +#include "AbstractBlock.h" +#include "AbstractInterface.h" +#include "ConnectedInterface.h" +#include "GroupScene.h" + + +GroupItem::GroupItem(BoxItem *_parentItem, + AbstractBlock *_refBlock, + Dispatcher *_dispatcher, + Parameters *_params) throw(Exception) :AbstractBoxItem( _refBlock, _dispatcher, _params) { + + parentItem = _parentItem; + + /* + minimumBoxWidth = nameWidth+2*nameMargin; + minimumBoxHeight = 100; + boxHeight = minimumBoxHeight; + boxWidth = minimumBoxWidth; + */ + rectTitle = QRectF(0,-(nameHeight+2*nameMargin),nameWidth+2*nameMargin,nameHeight+2*nameMargin); + /* + totalHeight = boxHeight + rectTitle.height(); + totalWidth = boxWidth; +*/ + selected = false; + + + setZValue(-100); + + setFlags(QGraphicsItem::ItemIsMovable | QGraphicsItem::ItemIsSelectable | QGraphicsItem::ItemSendsGeometryChanges); + + updateGeometry(); + QPointF initPos = QPointF(0.0,0.0) - originPoint; + setPos(initPos); + cout << "total size of group: " << totalWidth << "," << totalHeight << endl; + cout << "pos in scene: " << x() << "," << y() << endl; +} + +GroupItem::~GroupItem() { + // since the reference block is nowhere referenced except in this class, it must be deleted here + delete refBlock; +} + +bool GroupItem::isGroupItem() { + return true; +} + +BoxItem* GroupItem::getParentItem() { + return parentItem; +} + +void GroupItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) { + if(boxWidth > 0 && boxHeight > 0){ + if(selected) + painter->setPen(Qt::red); + else + painter->setPen(Qt::black); + + painter->drawRect(0,0,boxWidth,boxHeight); + painter->drawRect(rectTitle); + painter->drawText(rectTitle,Qt::AlignCenter | Qt::TextWordWrap,refBlock->getName()); + } + foreach(InterfaceItem *item, interfaces){ + item->paint(painter); + } +} + +/* NOTE: + Each time a new block is added in a GroupItem, it is placed + at an absolute position of (0,0) within the GroupItem. (NB: the absolute + position is computed by item.pos()+item.getOriginPoint()). + Thus, there are 3 cases : + - a single block is within the GroupItem + - several blocks already placed and a new one + - several blocks already placed and one is moving. + + */ +void GroupItem::updateMinimumSize() { + + // compute place taken by blocks. + int marginConn = 2*(params->arrowWidth+params->arrowLineLength); + if (rectTitle.width() > 2*marginConn) { + minimumBoxWidth = rectTitle.width(); + } + else { + minimumBoxWidth = 2*marginConn; + } + minimumBoxHeight = 2*marginConn; + + if (getScene() == NULL) return; + + QList blocks = getScene()->getBlockItems(); + if(blocks.length() > 0) { + // first, search for blocks that are at (0,0) + int xMaxZero = 0; + int yMaxZero = 0; + int xMax = 0; + int yMax = 0; + bool isZeroBlocks = false; + bool isOtherBlocks = false; + foreach(BoxItem* item, blocks) { + QPointF p = item->pos() + item->getOriginPoint(); + if ((p.x()==0.0) && (p.y()==0.0)) { + isZeroBlocks = true; + if (item->getTotalWidth() > xMaxZero) { + xMaxZero = item->getTotalWidth(); + } + if (item->getTotalHeight() > yMaxZero) { + yMaxZero = item->getTotalHeight(); + } + } + else { + isOtherBlocks = true; + if(p.x()+item->getTotalWidth() > xMax) { + xMax = p.x()+item->getTotalWidth(); + } + if(p.y()+item->getTotalHeight() > yMax) { + yMax = p.y()+item->getTotalHeight(); + } + } + } + if (isZeroBlocks) { + if (!isOtherBlocks) { + minimumBoxWidth = xMaxZero+2*marginConn; + minimumBoxHeight = yMaxZero+2*marginConn; + } + else { + if (xMaxZero+marginConn > xMax) { + minimumBoxWidth = xMaxZero+2*marginConn; + } + else { + minimumBoxWidth = xMax+marginConn; + } + if (yMaxZero+marginConn > yMax) { + minimumBoxHeight = yMaxZero+2*marginConn; + } + else { + minimumBoxHeight = yMax+marginConn; + } + } + } + else { + minimumBoxWidth = xMax+marginConn; + minimumBoxHeight = yMax+marginConn; + } + } + //cout << "min group size: " << minimumBoxWidth << "," << minimumBoxHeight << endl; +} + +void GroupItem::updateShape() { + updateGeometry(); +} + +bool GroupItem::updateGeometry(ChangeType type) { + + QPointF oldOrigin = originPoint; + QSize oldSize(totalWidth,totalHeight); + + updateMinimumSize(); + bool boxSizeChanged = false; + if (boxWidth < minimumBoxWidth) { + boxWidth = minimumBoxWidth; + boxSizeChanged = true; + } + if (boxHeight < minimumBoxHeight) { + boxHeight = minimumBoxHeight; + boxSizeChanged = true; + } + if (boxSizeChanged) { + updateInterfacesAndConnections(); + } + + // compute the max. width of interfaces' name for 4 orientations. + int maxSouth = 0; + int maxNorth = 0; + int maxEast = 0; + int maxWest = 0; + int ifaceWidth = 0; + + foreach(InterfaceItem* iface, interfaces) { + ifaceWidth = iface->getNameWidth(); + if (iface->getOrientation() == Parameters::South) { + if (ifaceWidth > maxSouth) maxSouth = ifaceWidth+ifaceMargin+params->arrowWidth+params->arrowLineLength; + } + else if (iface->getOrientation() == Parameters::North) { + if (ifaceWidth > maxNorth) maxNorth = ifaceWidth+ifaceMargin+params->arrowWidth+params->arrowLineLength; + } + else if (iface->getOrientation() == Parameters::East) { + if (ifaceWidth > maxEast) maxEast = ifaceWidth+ifaceMargin+params->arrowWidth+params->arrowLineLength; + } + else if (iface->getOrientation() == Parameters::West) { + if (ifaceWidth > maxWest) maxWest = ifaceWidth+ifaceMargin+params->arrowWidth+params->arrowLineLength; + } + } + double x = 0.0; + double y = 0.0; + totalWidth = boxWidth+maxEast; + totalHeight = boxHeight+maxSouth; + + if (maxWest > 0) { + x -= maxWest; + totalWidth += maxWest; + } + if (maxNorth > (nameHeight+2*nameMargin)) { + y -= maxNorth; + totalHeight += maxNorth; + } + else { + y -= nameHeight+2*nameMargin; + totalHeight += nameHeight+2*nameMargin; + } + QSizeF newSize(totalWidth,totalHeight); + originPoint.setX(x); + originPoint.setY(y); + + if ((boxSizeChanged) || (newSize != oldSize) || (originPoint != oldOrigin)) { + //cout << "must change group item shape" << endl; + prepareGeometryChange(); + return true; + } + return false; +} + +void GroupItem::mouseMoveEvent(QGraphicsSceneMouseEvent *event) { + + if(params->editState == Parameters::EditGroupMove) { + QPointF absPos = currentPosition + originPoint; + int gapX = event->scenePos().x() - cursorPosition.x(); + int gapY = event->scenePos().y() - cursorPosition.y(); + + //cout << "block abs. pos: " << absPos.x() << "," << absPos.y() << " | "; + //cout << "block current. pos: " << currentPosition.x() << "," << currentPosition.y() << " | "; +/* + if (absPos.x()+gapX < 0) { + gapX = -absPos.x(); + } + if (absPos.y()+gapY < 0) { + gapY = -absPos.y(); + } + */ + //cout << "gap: " << gapX << "," << gapY << " | "; + //cout << "scene: " << getScene()->sceneRect().x() << "," << getScene()->sceneRect().y() << endl; + QPointF gap(gapX,gapY); + currentPosition = currentPosition+gap; + setPos(currentPosition); + cursorPosition = event->scenePos(); + } + else if(params->editState == Parameters::EditGroupResize) { + + int gapX = event->scenePos().x() - cursorPosition.x(); + int gapY = event->scenePos().y() - cursorPosition.y(); + //cout << "gap: " << gapX << "," << gapY << endl; + switch(currentBorder){ + case BorderEast: { + if(boxWidth+gapX > minimumBoxWidth){ + boxWidth += gapX; + } + break; + } + case BorderSouth: { + if(boxHeight+gapY > minimumBoxHeight){ + boxHeight += gapY; + } + break; + } + case CornerSouthEast: { + if(boxWidth+gapX > minimumBoxWidth){ + boxWidth += gapX; + } + if(boxHeight+gapY > minimumBoxHeight){ + boxHeight += gapY; + } + break; + } + case NoBorder: + cout << "abnormal case while resizing block" << endl; + break; + } + updateGeometry(); + /* + // recompute the geometry of the block + updateGeometry(); + // update all interfaces positions + foreach(InterfaceItem *item, interfaces){ + item->updatePosition(); + } + // update all connections from/to this block + foreach(ConnectionItem *item, getScene()->getConnectionItems()){ + if ((item->getFromInterfaceItem()->getOwner() == this) || (item->getToInterfaceItem()->getOwner() == this)) { + item->setPathes(); + } + } + */ + cursorPosition = event->scenePos(); + } + else if(params->editState == Parameters::EditInterfaceMove) { + prepareGeometryChange(); + deplaceInterface(event->pos()); + // recompute the geometry of the block + updateGeometry(); + // update connection from/to the selected interface + foreach(ConnectionItem *item, getScene()->getConnectionItems()){ + if ((item->getFromInterfaceItem() == currentInterface) || (item->getToInterfaceItem() == currentInterface)) { + item->setPathes(); + } + } + update(); + } +} + +void GroupItem::mousePressEvent(QGraphicsSceneMouseEvent *event) { + + QPointF pos = event->pos(); + qreal x = pos.x(); + qreal y = pos.y(); + + QGraphicsItem::mousePressEvent(event); + + if(event->button() == Qt::RightButton) return; + + int mode = getScene()->getEditionMode(); + + dispatcher->setCurrentGroupWidget(getScene()->getGroupWindow()); + + /* NOTE : commneted because group interface are normally + created and the connected directly to a block within + the group. Furthermore, there can be a single connection + from a groupe interface. + + if ((mode == GroupScene::AddConnection) && (params->cursorState == Parameters::CursorOnInterface)) { + InterfaceItem *inter = getInterfaceFromCursor(x,y); + if (inter != NULL) { + + if (params->editState == Parameters::EditNoOperation) { + getScene()->setSelectedInterface(1,inter); + params->setEditState(Parameters::EditStartConnection); + } + else if (params->editState == Parameters::EditStartConnection) { + if (inter == getScene()->getSelectedInterface(1)) { + params->setEditState(Parameters::EditAbortConnection); + } + else { + getScene()->setSelectedInterface(2,inter); + params->setEditState(Parameters::EditCloseConnection); + } + } + } + } + */ + if (mode == GroupScene::ItemEdition) { + + if (params->cursorState == Parameters::CursorOnInterface) { + InterfaceItem *inter = getInterfaceFromCursor(x,y); + if (inter != NULL) { + currentInterface = inter; + params->setEditState(Parameters::EditInterfaceMove); + } + } + else if (params->cursorState == Parameters::CursorInGroupTitle) { + params->setEditState(Parameters::EditGroupMove); + cursorPosition = event->scenePos(); + } + else if (params->cursorState == Parameters::CursorOnBorder) { + setFlag(ItemIsMovable, false); + cursorPosition = event->scenePos(); + params->setEditState(Parameters::EditGroupResize); + } + } +} + +void GroupItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) { + + int mode = getScene()->getEditionMode(); + + /* NOTE : commneted because group interface are normally + created and the connected directly to a block within + the group. Furthermore, there can be a single connection + from a groupe interface. + + if (mode == GroupScene::AddConnection) { + + if (params->editState == Parameters::EditStartConnection) { + params->setEditState(Parameters::EditStartConnection); + InterfaceItem* iface = getScene()->getSelectedInterface(1); + iface->selected = true; + update(iface->boundingRect()); + } + else if (params->editState == Parameters::EditAbortConnection) { + InterfaceItem* iface = getScene()->getSelectedInterface(1); + iface->selected = false; + update(iface->boundingRect()); + getScene()->setSelectedInterface(1,NULL); + params->setEditState(Parameters::EditNoOperation); + } + else if (params->editState == Parameters::EditCloseConnection) { + InterfaceItem* iface1 = getScene()->getSelectedInterface(1); + InterfaceItem* iface2 = getScene()->getSelectedInterface(2); + bool ok = dispatcher->connect(iface1,iface2); + if (ok) { + iface1->selected = false; + update(iface1->boundingRect()); + getScene()->setSelectedInterface(1,NULL); + getScene()->setSelectedInterface(2,NULL); + params->setEditState(Parameters::EditNoOperation); + } + } + } + */ + if (mode == GroupScene::ItemEdition) { + currentInterface = NULL; + setFlag(ItemIsMovable, true); + params->editState = Parameters::EditNoOperation; + } + QGraphicsItem::mouseReleaseEvent(event); +} + +void GroupItem::hoverMoveEvent(QGraphicsSceneHoverEvent *event) { + QPointF pos = event->pos(); + qreal x = pos.x(); + qreal y = pos.y(); + currentBorder = NoBorder; + int mode = getScene()->getEditionMode(); + + if (mode == GroupScene::AddConnection) { + InterfaceItem* iface = getInterfaceFromCursor(x,y); + if (iface != NULL) { + params->cursorState = Parameters::CursorOnInterface; + setCursor(Qt::PointingHandCursor); + } + else { + params->cursorState = Parameters::CursorNowhere; + setCursor(Qt::ArrowCursor); + } + } + else if (mode == GroupScene::ItemEdition) { + int marginE = 5; + int marginS = 5; + + InterfaceItem* iface = getInterfaceFromCursor(x,y); + if (iface != NULL) { + params->cursorState = Parameters::CursorOnInterface; + setCursor(Qt::PointingHandCursor); + } + else if ((x>boxWidth-marginE)&&(xcursorState = Parameters::CursorOnBorder; + + if ((y>boxHeight-2*marginS)&&(yboxHeight-marginS)&&(ycursorState = Parameters::CursorOnBorder; + + if ((x>boxWidth-2*marginE)&&(xcursorState = Parameters::CursorInGroupTitle; + setCursor(Qt::OpenHandCursor); + } + else { + params->cursorState = Parameters::CursorNowhere; + setCursor(Qt::ArrowCursor); + } + } + } + QGraphicsItem::hoverMoveEvent(event); +} + +void GroupItem::contextMenuEvent(QGraphicsSceneContextMenuEvent *event) { + QMenu menu; + QAction* showProperties = NULL; + QAction* removeAction = NULL; + QAction* renameAction = NULL; + + InterfaceItem* ifaceItem = getInterfaceFromCursor(event->pos().x(), event->pos().y()); + if( ifaceItem != NULL){ + showProperties = menu.addAction("Show properties"); + renameAction = menu.addAction("Rename"); + menu.addSeparator(); + /* CAUTION : the interface can be removed only if its + connected to only one side, i.e. connectedFrom is null + or connectedTo is empty. + + */ + ConnectedInterface* ref = ifaceItem->refInter; + if ((!ref->isConnectedFrom()) || (!ref->isConnectedTo())) { + removeAction = menu.addAction("Remove"); + } + } + else { + renameAction = menu.addAction("Rename"); + } + QAction* selectedAction = menu.exec(event->screenPos()); + + if(selectedAction == NULL) return; + + if(selectedAction == renameAction){ + if(ifaceItem != NULL) + dispatcher->rename(ifaceItem); + else + dispatcher->rename(this); + } + else if(selectedAction == showProperties){ + dispatcher->showProperties(ifaceItem); + } + else if (selectedAction == removeAction) { + dispatcher->removeGroupInterface(ifaceItem); + } +} + +InterfaceItem* GroupItem::isHoverInterface(QPointF point) { + foreach(InterfaceItem *inter, interfaces){ + if(inter->boundingRect().contains(point)) + return inter; + } + return NULL; +} + +void GroupItem::save(QXmlStreamWriter &writer) { + + writer.writeStartElement("group_item"); + + QString attrId = QString::number(id); + QString attrName(getRefBlock()->getName()); + QString attrUpperItem = QString::number(-1); + if(parentItem != NULL){ + attrUpperItem = QString::number(parentItem->getId()); + } + QString attrPos = QString::number(pos().x()).append(",").append(QString::number(pos().y())); + QString attrDim = QString::number(getWidth()).append(",").append(QString::number(getHeight())); + + + writer.writeAttribute("id",attrId); + writer.writeAttribute("upper_item",attrUpperItem); + writer.writeAttribute("name",attrName); + writer.writeAttribute("position", attrPos); + writer.writeAttribute("dimension", attrDim); + + writer.writeStartElement("group_ifaces"); + + writer.writeAttribute("count",QString::number(interfaces.length())); + + foreach(InterfaceItem *item, interfaces){ + writer.writeStartElement("group_iface"); + + writer.writeAttribute("id",QString::number(item->getId())); + writer.writeAttribute("name",item->getName()); + writer.writeAttribute("level",QString(item->refInter->getLevelString())); + writer.writeAttribute("direction",QString(item->refInter->getDirectionString())); + writer.writeAttribute("orientation",item->getStrOrientation()); + writer.writeAttribute("position",QString::number(item->getPositionRatio())); + + writer.writeEndElement(); + } + + writer.writeEndElement();// + + writer.writeEndElement();// +} diff --git a/GroupItem.h b/GroupItem.h new file mode 100644 index 0000000..3882a60 --- /dev/null +++ b/GroupItem.h @@ -0,0 +1,73 @@ +#ifndef __GROUPITEM_H__ +#define __GROUPITEM_H__ + +#include + +#include +#include + +#include "AbstractBoxItem.h" +class AbstractBoxItem; + +class BoxItem; +class InterfaceItem; +class Dispatcher; +class Parameters; + +#include "Exception.h" + +using namespace std; +using namespace Qt; + +class GroupItem : public AbstractBoxItem { + +public: + /* CAUTION : the parentItem of a group block IS NOT the group item of the parent scene. It is a BlockItem that is + wihtin the parent scene. It is used when interfaceItem are added to the current GroupItem: they must be also added + to the parent BlockItem. + + NB : this yields a problem when loading a project because scenes are created before creating group and then block. + thus, the parentItem must be initialized afterward when all block items have been created. + + */ + GroupItem(BoxItem* _parentItem, AbstractBlock *_refBlock, Dispatcher *_dispatcher, Parameters *_params) throw(Exception); + ~GroupItem(); + + // getters + BoxItem* getParentItem(); + + // setters + + // testers + bool isGroupItem(); + + // others + void updateShape(); + void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = 0); + void save(QXmlStreamWriter& writer); + +protected: + + void updateMinimumSize(); // modify the minimum size + bool updateGeometry(ChangeType type); + + void mousePressEvent(QGraphicsSceneMouseEvent *event); + void mouseMoveEvent(QGraphicsSceneMouseEvent *event); + void mouseReleaseEvent(QGraphicsSceneMouseEvent *event); + void hoverMoveEvent(QGraphicsSceneHoverEvent *event); + void contextMenuEvent(QGraphicsSceneContextMenuEvent *event); + + + +private: + /* NOTE : parentItem attribute is never NULL except for the top GroupItem + in the top scene + */ + BoxItem* parentItem; + QRectF rectTitle; + + + InterfaceItem *isHoverInterface(QPointF point); +}; + +#endif // __GROUPITEM_H__ diff --git a/GroupScene.cpp b/GroupScene.cpp new file mode 100644 index 0000000..7ba8c9d --- /dev/null +++ b/GroupScene.cpp @@ -0,0 +1,215 @@ +#include "GroupScene.h" + +#include "Dispatcher.h" +#include "Parameters.h" +#include "GroupWidget.h" +#include "GroupItem.h" +#include "BoxItem.h" +#include "ConnectionItem.h" +#include "InterfaceItem.h" +#include "AbstractBlock.h" + +GroupScene::GroupScene(GroupScene *_parentScene, GroupWidget *_window, Dispatcher* _dispatcher, Parameters* _params, bool _topScene, QObject *parent) : QGraphicsScene(parent) { + dispatcher = _dispatcher; + params = _params; + parentScene = _parentScene; + if (parentScene != NULL) { + parentScene->addChildScene(this); + } + window = _window; + groupItem = NULL; + id = -1; + topScene = _topScene; + editMode = InitState; + + selectedInterfaces[0] = NULL; + selectedInterfaces[1] = NULL; +} + +GroupScene::~GroupScene() { + blockItems.clear(); + connectionItems.clear(); + groupItem = NULL; +} + +void GroupScene::setSelectedInterface(int id, InterfaceItem* iface) { + if ((id < 1)|| (id > 2)) return; + selectedInterfaces[id-1] = iface; +} + +InterfaceItem* GroupScene::getSelectedInterface(int id) { + if ((id < 1)|| (id > 2)) return NULL; + return selectedInterfaces[id-1]; +} + +QList GroupScene::getSelectedBlocks() { + QList lst; + foreach(BoxItem *item, blockItems){ + if (item->isSelected()){ + lst.append(item); + } + } + return lst; +} + +int GroupScene::setItemsId(int countInit) { + int counter = countInit; + groupItem->setId(counter++); + foreach(BoxItem *item, blockItems){ + item->setId(counter++); + } + return counter; +} + +int GroupScene::setInterfacesId(int countInit) { + int counter = countInit; + foreach(InterfaceItem* inter, groupItem->getInterfaces()){ + inter->setId(counter++); + } + foreach(BoxItem *item, blockItems){ + foreach(InterfaceItem* inter, item->getInterfaces()){ + inter->setId(counter++); + } + } + return counter; +} + +QList GroupScene::getGroupAndBlocks() { + + QList lst; + lst.append(groupItem); + foreach(BoxItem *item, blockItems){ + lst.append(item); + } + return lst; +} + +void GroupScene::createBlockItem(AbstractBlock *block) { + + BoxItem* blockItem = new BoxItem(block,dispatcher,params,groupItem); + blockItem->setZValue(1); + addBlockItem(blockItem); +} + +void GroupScene::addBlockItem(BoxItem* item) { + // add item from the viewport + //addItem(item); + // add item from the QList + blockItems.append(item); + // repainting the group + groupItem->updateShape(); + // center the new block + QPointF newPos((groupItem->getWidth()-item->getTotalWidth())/2.0, (groupItem->getHeight()-item->getTotalHeight())/2.0); + newPos = newPos-item->getOriginPoint(); + item->moveTo(newPos); +} + +void GroupScene::removeBlockItem(BoxItem* item) { + // remove item from the viewport + removeItem(item); + // remove item from the QList + blockItems.removeAll(item); + // repainting the group + groupItem->updateShape(); +} + +void GroupScene::createConnectionItem(InterfaceItem *iface1, InterfaceItem *iface2) { + ConnectionItem* conn = new ConnectionItem(iface1,iface2, dispatcher, params, groupItem); + addConnectionItem(conn); +} + +ConnectionItem* GroupScene::searchConnectionItem(InterfaceItem *iface1, InterfaceItem *iface2) { + foreach(ConnectionItem* conn , connectionItems) { + if ( ((conn->getFromInterfaceItem() == iface1) && (conn->getToInterfaceItem() == iface2)) || + ((conn->getFromInterfaceItem() == iface2) && (conn->getToInterfaceItem() == iface1))) { + return conn; + } + } +} + +void GroupScene::addConnectionItem(ConnectionItem* item) { + // add item from the viewport + //addItem(item); + // add item from the QList + connectionItems.append(item); +} + +void GroupScene::removeConnectionItem(ConnectionItem* item) { + // remove item from the viewport + removeItem(item); + // remove item from the QList + connectionItems.removeAll(item); +} + +void GroupScene::setGroupItem(GroupItem *group) { + if ((groupItem == NULL) && (group != NULL)) { + groupItem = group; + addItem(group); + } +} + +void GroupScene::removeGroupItem() { + // remove item from the viewport + removeItem(groupItem); + groupItem = NULL; +} + +QList GroupScene::getInterfaceConnections(InterfaceItem *item) { + QList list; + foreach(ConnectionItem *conn, connectionItems){ + if(conn->getFromInterfaceItem() == item || conn->getToInterfaceItem() == item){ + list.append(conn); + } + } + return list; +} + +void GroupScene::unselecteInterfaces() { + + if (selectedInterfaces[0] != NULL) { + selectedInterfaces[0]->selected = false; + selectedInterfaces[0] == NULL; + } + if (selectedInterfaces[1] != NULL) { + selectedInterfaces[1]->selected = false; + selectedInterfaces[1] == NULL; + } +} + +void GroupScene::updateConnectionItemsShape() { + + foreach(ConnectionItem* conn, connectionItems){ + conn->setPathes(); + } +} + +void GroupScene::save(QXmlStreamWriter &writer) { + writer.writeStartElement("scene"); + writer.writeAttribute("id",QString::number(id)); + groupItem->save(writer); + + writer.writeStartElement("block_items"); + + QList functionalBlocks; + QList groupBlocks; + + foreach(BoxItem *item, blockItems){ + if(item->getRefBlock()->isFunctionalBlock()){ + functionalBlocks.append(item); + } else if(item->getRefBlock()->isGroupBlock()){ + groupBlocks.append(item); + } + } + writer.writeAttribute("functional_count",QString::number(functionalBlocks.length())); + writer.writeAttribute("group_count",QString::number(groupBlocks.length())); + + foreach(BoxItem* item, functionalBlocks) { + item->save(writer); + } + foreach(BoxItem* item, groupBlocks) { + item->save(writer); + } + writer.writeEndElement(); // block_items + writer.writeEndElement(); // scene +} + diff --git a/GroupScene.h b/GroupScene.h new file mode 100644 index 0000000..8561482 --- /dev/null +++ b/GroupScene.h @@ -0,0 +1,110 @@ +#ifndef __GROUPSCENE_H__ +#define __GROUPSCENE_H__ + +#include +#include +#include + +class Dispatcher; +class Parameters; +class AbstractBlock; +class GroupWidget; +class GroupItem; +class BoxItem; +class AbstractBoxItem; +class ConnectionItem; +class InterfaceItem; + +using namespace std; +using namespace Qt; + +/* NOTES : + + - A GroupScene is composed of a single GroupItem that contains BlockItem and ConnectionItem + The GroupItem is stored in the mainGroup attribute + + - A GroupScene is instanciated whenever there is a GroupItem that must be created, i.e. + for the top scene or for subgroups. + + - This class is a subclass of QGraphicsScene by convenience but it's goal is more to list + the different inner items. Thus, all operations that add/remove items to the scene will also + add them in the QList + */ + +class GroupScene : public QGraphicsScene { + +public: + + /* edition mode of the window: + - AddBlock: can only add blocks to the scene + - AddConnection: can only add connections to the scene + - AddGroup: while a new group (empty or from selected blocks) is created + - ItemEdtion: can move/resize blocks/interfaces, remove blocks/interface/group, ... + */ + enum EditMode { InitState, AddBlock, AddConnection, AddGroup, ItemEdition }; + + GroupScene(GroupScene* _parentScene, GroupWidget* _window, Dispatcher* _dispatcher, Parameters* _params, bool topScene = false, QObject *parent = 0); + ~GroupScene(); + + // attributes getters + inline GroupItem* getGroupItem() {return groupItem;} + inline QList getBlockItems() { return blockItems; } + inline QList getConnectionItems() { return connectionItems; } + inline QList getChildrenScene() { return childrenScene; } + inline GroupWidget* getGroupWindow() { return window; } + inline int getId() { return id; } + inline EditMode getEditionMode() { return editMode; } + InterfaceItem* getSelectedInterface(int id); + + // attributes setters + inline void setWindow(GroupWidget* _window) { window = _window; } + void setGroupItem(GroupItem* group); + inline void setId(int id) { this->id = id; } + inline void setEditionMode(EditMode mode) { editMode = mode; } + void setSelectedInterface(int id, InterfaceItem* iface); + + // attributes testers + inline bool isTopScene() { return topScene; } + + + // others + void createBlockItem(AbstractBlock* block); + void addBlockItem(BoxItem* item); + void removeBlockItem(BoxItem* item); + void createConnectionItem(InterfaceItem* iface1, InterfaceItem* iface2); + ConnectionItem* searchConnectionItem(InterfaceItem* iface1, InterfaceItem* iface2); + void addConnectionItem(ConnectionItem* item); + void removeConnectionItem(ConnectionItem* item); + void removeGroupItem(); + inline void addChildScene(GroupScene* child) { childrenScene.append(child); } + void unselecteInterfaces(); + + QList getGroupAndBlocks(); + QList getSelectedBlocks(); + + QList getInterfaceConnections(InterfaceItem *item); + + int setItemsId(int countInit=1); + int setInterfacesId(int countInit=1); + + void updateConnectionItemsShape(); + + void save(QXmlStreamWriter& writer); + +private: + Dispatcher *dispatcher; + Parameters *params; + GroupScene* parentScene; // the parnet scene, =NULL for top scene + GroupWidget* window; // the GroupWindow that contains that scene + int id; + GroupItem *groupItem; // mandatory to be an instance of GroupItem. + QList connectionItems; + QList blockItems; + QList childrenScene; + bool topScene; + EditMode editMode; + InterfaceItem* selectedInterfaces[2]; // selected iface 1 in AddConnection mode + +}; + +#endif // __GROUPSCENE_H__ diff --git a/GroupWidget.cpp b/GroupWidget.cpp new file mode 100644 index 0000000..1446a13 --- /dev/null +++ b/GroupWidget.cpp @@ -0,0 +1,279 @@ +#include "GroupWidget.h" + +#include "Dispatcher.h" +#include "Parameters.h" + +#include "GroupScene.h" +#include "GroupBlock.h" +#include "BoxItem.h" +#include "GroupItem.h" +#include "ConnectionItem.h" +#include "Graph.h" + +GroupWidget::GroupWidget(GroupWidget *_upperGroup, Dispatcher *_dispatcher, + Parameters *_params, + QWidget *parent) : QWidget(parent) { + upperGroup = _upperGroup; + dispatcher = _dispatcher; + params = _params; + if (upperGroup == NULL) { + topGroup = true; + scene = new GroupScene(NULL, this, dispatcher, params, true); + } + else { + topGroup = true; + scene = new GroupScene(upperGroup->getScene(), this, dispatcher, params, false); + } + + layout = new QGridLayout; + + QGraphicsView *view = new QGraphicsView(scene); + layout->addWidget(view,1,0,1,3); + + createActions(); + createToolbar(); + setLayout(layout); + setFocusPolicy(Qt::StrongFocus); + setFocus(); + + scene->setEditionMode(GroupScene::ItemEdition); + updateBlockButton(); + +} + +GroupWidget::~GroupWidget(){} + +void GroupWidget::changeConnectionMode(int mode) { + /* + QPalette pal = buttonNewConnection->palette(); + + if(mode == -1){ + + if(params->sceneMode != Parameters::EditOnConnection){ + params->sceneMode = Parameters::EditOnConnection; + pal.setColor(QPalette::Button, QColor(Qt::lightGray)); + + } else { + params->sceneMode = Parameters::EditNoOperation; + pal.setColor(QPalette::Button, QColor("#edeceb")); + dispatcher->unselectAllInterfaces(); + } + } else if(mode == Parameters::EditOnConnection){ + params->sceneMode = Parameters::EditOnConnection; + pal.setColor(QPalette::Button, QColor(Qt::lightGray)); + } else { + params->sceneMode = Parameters::EditNoOperation; + pal.setColor(QPalette::Button, QColor("#edeceb")); + } + dispatcher->unselectAllInterfaces(); + buttonNewConnection->setAutoFillBackground(true); + buttonNewConnection->setPalette(pal); + buttonNewConnection->update(); + */ +} + + +void GroupWidget::enableGroupButton(bool b) { + newGroupAct->setEnabled(b); +} + + +void GroupWidget::mousePressEvent(QMouseEvent *e) { + dispatcher->setCurrentGroupWidget(this); + QWidget::mousePressEvent(e); +} + +void GroupWidget::focusInEvent(QFocusEvent *e) { + /* + if(params->currentWindow != this){ + params->setCurrentWindow(this); + changeConnectionMode(false); + } + */ +} + +void GroupWidget::closeEvent(QCloseEvent *e) { + clearFocus(); + focusNextChild(); +} + + +void GroupWidget::createActions() { + + butAddConnection = new QToolButton(this); + butAddConnection->setIcon(QPixmap::fromImage(QImage("icons/add_connection.png"))); + butAddConnection->setToolTip("Adding connections"); + QPalette pal = butAddConnection->palette(); + butBaseColor = pal.color(QPalette::Button); + connect(butAddConnection, SIGNAL(clicked()), this, SLOT(slotAddConnection())); + + butEdit = new QToolButton(this); + butEdit->setIcon(QPixmap::fromImage(QImage("icons/edit_block.png"))); + butEdit->setToolTip("Edit items"); + connect(butEdit, SIGNAL(clicked()), this, SLOT(slotEdit())); + + copyBlockAct = new QAction(tr("&Copy block"), this); + copyBlockAct->setIcon(QPixmap::fromImage(QImage("icons/copy.png"))); + copyBlockAct->setStatusTip(tr("Copy the selected block")); + connect(copyBlockAct, SIGNAL(triggered()), this, SLOT(slotCopyBlock())); + + newEmptyGroupAct = new QAction(tr("&Create a new empty group"), this); + newEmptyGroupAct->setIcon(QPixmap::fromImage(QImage("icons/add_group_void.png"))); + newEmptyGroupAct->setStatusTip(tr("Create a new empty group")); + connect(newEmptyGroupAct, SIGNAL(triggered()), this, SLOT(slotNewEmptyGroup())); + + newGroupAct = new QAction(tr("&Create a new group"), this); + newGroupAct->setIcon(QPixmap::fromImage(QImage("icons/add_group_select.png"))); + newGroupAct->setStatusTip(tr("Create a new group")); + newGroupAct->setDisabled(true); + connect(newGroupAct, SIGNAL(triggered()), this, SLOT(slotNewGroup())); + + deleteAct = new QAction(tr("&Delete selected elements"), this); + deleteAct->setIcon(QPixmap::fromImage(QImage("icons/delete.png"))); + deleteAct->setStatusTip(tr("Delete selected elements")); + connect(deleteAct, SIGNAL(triggered()), this, SLOT(slotDeleteItems())); + + selectAllAct = new QAction(tr("&Select all elements"), this); + selectAllAct->setIcon(QPixmap::fromImage(QImage("icons/delete.png"))); + selectAllAct->setStatusTip(tr("Select all elements")); + connect(selectAllAct, SIGNAL(triggered()), this, SLOT(slotSelectAll())); + + unselectAllAct = new QAction(tr("&unselect all elements"), this); + unselectAllAct->setIcon(QPixmap::fromImage(QImage("icons/delete.png"))); + unselectAllAct->setStatusTip(tr("Unselect all elements")); + connect(unselectAllAct, SIGNAL(triggered()), this, SLOT(slotUnselectAll())); +} + +void GroupWidget::createToolbar() { + toolbarEditMode = new QToolBar(tr("Mode")); + toolbarAdd = new QToolBar(tr("Group")); + toolbarTools = new QToolBar(tr("Tools")); + + toolbarEditMode->addWidget(new QLabel("Mode")); + toolbarTools->addWidget(new QLabel("Tools")); + + toolbarEditMode->addSeparator(); + toolbarTools->addSeparator(); + + toolbarEditMode->addWidget(butAddConnection); + toolbarEditMode->addWidget(butEdit); + + toolbarAdd->addAction(copyBlockAct); + toolbarAdd->addAction(newEmptyGroupAct); + toolbarAdd->addAction(newGroupAct); + + toolbarTools->addAction(deleteAct); + toolbarTools->addAction(selectAllAct); + toolbarTools->addAction(unselectAllAct); + + layout->addWidget(toolbarEditMode,0,0); + layout->addWidget(toolbarAdd,0,1); + layout->addWidget(toolbarTools,0,2); +} + +void GroupWidget::slotEdit() { + dispatcher->unselectAllItems(); + getScene()->setEditionMode(GroupScene::ItemEdition); + updateBlockButton(); +} + +void GroupWidget::slotCopyBlock() { + foreach (BoxItem *item, params->getCurrentScene()->getBlockItems()) { + if(item->isSelected()){ + dispatcher->duplicateBlock(item); + } + } +} + +void GroupWidget::slotAddConnection() { + dispatcher->unselectAllItems(); + getScene()->setEditionMode(GroupScene::AddConnection); + updateBlockButton(); +} + +void GroupWidget::updateBlockButton() { + + // reset all buttons to light gray + QPalette pal = butAddConnection->palette(); + pal.setColor(QPalette::Button, butBaseColor); + + butAddConnection->setAutoFillBackground(true); + butAddConnection->setPalette(pal); + butAddConnection->update(); + butEdit->setAutoFillBackground(true); + butEdit->setPalette(pal); + butEdit->update(); + + // set the good one to dark gray + pal.setColor(QPalette::Button, QColor(Qt::darkGray)); + + if(getScene()->getEditionMode() == GroupScene::AddConnection) { + butAddConnection->setAutoFillBackground(true); + butAddConnection->setPalette(pal); + butAddConnection->update(); + } + else if(getScene()->getEditionMode() == GroupScene::ItemEdition) { + butEdit->setAutoFillBackground(true); + butEdit->setPalette(pal); + butEdit->update(); + } + +} + +void GroupWidget::slotNewEmptyGroup() { + + // creating the GroupBlock in graph model + GroupBlock* groupBlock = params->addGroupBlock(); + // creating the BlockItem in the inner scene + BoxItem* block = new BoxItem(groupBlock, dispatcher, params, scene->getGroupItem()); + + GroupWidget* child = dispatcher->createChildScene(this,block); + child->show(); +} + +void GroupWidget::slotNewGroup() +{ + dispatcher->addNewFullGroup(); +} + +void GroupWidget::slotDeleteItems() { + foreach (BoxItem *item, scene->getBlockItems()) { + if(item->isSelected()){ + dispatcher->removeBlock(item); + } + } + foreach (ConnectionItem *item, scene->getConnectionItems()) { + if(item->isSelected()){ + dispatcher->removeConnection(item); + } + } +} + +void GroupWidget::slotSelectAll() +{ + foreach(QGraphicsItem *item, params->getCurrentScene()->items()){ + if(item->data(0) == "block"){ + BoxItem *b = dynamic_cast(item); + b->setSelected(true); + } else if(item->data(0) == "connection"){ + ConnectionItem *c = dynamic_cast(item); + c->setSelected(true); + } + item->update(item->boundingRect()); + } +} + +void GroupWidget::slotUnselectAll() +{ + foreach(QGraphicsItem *item, params->getCurrentScene()->items()){ + if(item->data(0) == "block"){ + BoxItem *b = dynamic_cast(item); + b->setSelected(false); + } else if(item->data(0) == "connection"){ + ConnectionItem *c = dynamic_cast(item); + c->setSelected(false); + } + + item->update(item->boundingRect()); + } +} diff --git a/GroupWidget.h b/GroupWidget.h new file mode 100644 index 0000000..bfdb78b --- /dev/null +++ b/GroupWidget.h @@ -0,0 +1,85 @@ +#ifndef __GROUPWIDGET_H__ +#define __GROUPWIDGET_H__ + +#include + +class Dispatcher; +class Parameters; +class GroupScene; + + + +class GroupWidget : public QWidget { + Q_OBJECT +public: + + explicit GroupWidget(GroupWidget* _upperGroup, Dispatcher* _dispatcher, Parameters* _params, QWidget *parent = 0); + + // getters + inline GroupScene *getScene() {return scene;} + inline QToolButton* getButtonNewConnection(){return butAddConnection;} + + // testers + inline bool isTopGroup() { return topGroup;} + + // others + void changeConnectionMode(int mode = -1); + + + ~GroupWidget(); + + void enableGroupButton(bool b); + +protected: + void mousePressEvent(QMouseEvent *e); + void focusInEvent(QFocusEvent *e); + void closeEvent(QCloseEvent *e); + +private: + GroupWidget* upperGroup; // NB:for convenience. NULL if it contains the top scene + bool topGroup; + + GroupScene *scene; + Dispatcher *dispatcher; + Parameters *params; + + void createActions(); + void createToolbar(); + + QToolButton* butAddConnection; + QToolButton* butEdit; + void updateBlockButton(); + QColor butBaseColor; + + QAction *copyBlockAct; + + QAction *newEmptyGroupAct; + QAction *newGroupAct; + + QAction *deleteAct; + QAction *selectAllAct; + QAction *unselectAllAct; + + QToolBar *toolbarEditMode; + QToolBar *toolbarAdd; + QToolBar *toolbarTools; + + + QGridLayout *layout; + +signals: + +private slots: + void slotCopyBlock(); + void slotAddConnection(); + void slotEdit(); + + void slotNewEmptyGroup(); + void slotNewGroup(); + + void slotDeleteItems(); + void slotSelectAll(); + void slotUnselectAll(); +}; + +#endif // __GROUPWIDGET_H__ diff --git a/InterfaceItem.cpp b/InterfaceItem.cpp new file mode 100644 index 0000000..552e159 --- /dev/null +++ b/InterfaceItem.cpp @@ -0,0 +1,445 @@ +#include "InterfaceItem.h" + +#include "Parameters.h" +#include "GroupInterface.h" +#include "FunctionalInterface.h" +#include "BoxItem.h" + +int InterfaceItem::counter = 0; + +InterfaceItem::InterfaceItem(double _position, + int _orientation, + ConnectedInterface *_refInter, + AbstractBoxItem* _owner, + Parameters* _params){ + positionRatio = _position; + orientation = _orientation; + refInter = _refInter; + + // CAUTION : the owner must add explicitely this item to its interface, calling addInterface() + owner = _owner; + params = _params; + selected = false; + name = refInter->getName(); + QFontMetrics fmName(params->defaultIfaceFont); + nameWidth = fmName.width(name); + nameHeight = fmName.height(); + // by default, only data interface are visible + if (refInter->getPurpose() == AbstractInterface::Data) { + visible = true; + } + else { + visible = false; + } + + this->id = InterfaceItem::counter++; + + updatePosition(); +} + + +InterfaceItem::InterfaceItem(){ + this->id = counter++; +} + +/* boundingRect() : give the bounding rect in the blockitem coord. system */ +QRectF InterfaceItem::boundingRect() const { + + QPointF pointHG; + QSizeF s; + + switch(orientation){ + case Parameters::East : + pointHG = QPointF(originPoint.x(),originPoint.y()-(params->arrowHeight/2.0)); + s = QSizeF(params->arrowWidth+params->arrowLineLength, params->arrowHeight); + break; + case Parameters::North : + pointHG = QPointF(originPoint.x()-(params->arrowHeight/2.0),originPoint.y()-params->arrowWidth-params->arrowLineLength); + s = QSizeF(params->arrowHeight,params->arrowWidth+params->arrowLineLength); + break; + case Parameters::West : + pointHG = QPointF(originPoint.x()-params->arrowLineLength-params->arrowWidth,originPoint.y()-(params->arrowHeight/2.0)); + s = QSizeF(params->arrowWidth+params->arrowLineLength, params->arrowHeight); + break; + case Parameters::South : + pointHG = QPointF(originPoint.x()-(params->arrowHeight/2.0),originPoint.y()); + s = QSizeF(params->arrowHeight, params->arrowWidth+params->arrowLineLength); + break; + default : + pointHG = QPointF(); + s = QSizeF(); + break; + } + + return QRectF(pointHG,s); +} + +void InterfaceItem::paint(QPainter *painter) { + + if(visible) { + + painter->save(); + + if(selected) { + painter->setPen(QPen(Qt::red,2)); + } + else if(refInter->getLevel() == AbstractInterface::Basic) { + painter->setPen(QPen(Qt::darkCyan,1)); + } + else if(refInter->getLevel() == AbstractInterface::Top) { + painter->setPen(QPen(Qt::black,1)); + } + + painter->translate(originPoint); + + switch(orientation) { + case Parameters::North: + painter->rotate(-90); + break; + case Parameters::West: + painter->rotate(180); + break; + case Parameters::South: + painter->rotate(90); + break; + } + + // draw arrows + if(refInter->getDirection() == AbstractInterface::Input) { + painter->drawPath(params->inArrow); + } + else if(refInter->getDirection() == AbstractInterface::Output) { + painter->drawPath(params->outArrow); + } else if(refInter->getDirection() == AbstractInterface::InOut) { + painter->drawPath(params->inArrow); + painter->drawPath(params->outArrow); + } + + // draw names + + // reset to normal if at west + if(orientation == Parameters::West){ + painter->rotate(180); + } + + painter->setFont(params->defaultIfaceFont); + + QFontMetrics fm = painter->fontMetrics(); + int w = nameWidth + owner->getIfaceMargin(); + int h = nameHeight; + + if(orientation == Parameters::West){ + + if(owner->isGroupItem()){ + painter->drawText(-(w+params->arrowWidth+params->arrowLineLength),-h/2,w,h,Qt::AlignLeft | Qt::TextWordWrap, refInter->getName()); + } + else if(owner->isBoxItem()){ + painter->drawText(0,-h/2,w,h,Qt::AlignRight | Qt::TextWordWrap, refInter->getName()); + } + } + else { + + if(owner->isGroupItem()) { + painter->drawText(params->arrowWidth+params->arrowLineLength,-h/2,w,h,Qt::AlignRight | Qt::TextWordWrap, refInter->getName()); + } + else if(owner->isBoxItem()) { + painter->drawText(-w,-h/2,w,h,Qt::AlignLeft | Qt::TextWordWrap, refInter->getName()); + } + } + + painter->restore(); + } +} + +QPointF InterfaceItem::getEndPointInGroup() { + QPointF p; + + if (owner->isGroupItem()) { + p = originPoint; + } + else { + double x = owner->x() + originPoint.x(); + double y = owner->y() + originPoint.y(); + switch(orientation){ + case Parameters::East: + x += params->arrowWidth+params->arrowLineLength; + break; + case Parameters::North: + y -= params->arrowWidth+params->arrowLineLength; + break; + case Parameters::West: + x -= params->arrowWidth+params->arrowLineLength; + break; + case Parameters::South: + y += params->arrowWidth+params->arrowLineLength; + break; + } + p = QPointF(x,y); + } + + //cout << "iface end point in group item: " << p.x() << "," << p.y() << endl; + return p; +} + +void InterfaceItem::setOriginPoint() { + switch(orientation){ + case Parameters::East: + originPoint = QPointF(owner->getWidth(),position); + break; + case Parameters::North: + originPoint = QPointF(position,0); + break; + case Parameters::West: + originPoint = QPointF(0,position); + break; + case Parameters::South: + originPoint = QPointF(position,owner->getHeight()); + break; + } +} + +QString InterfaceItem::getStrOrientation() { + QString str = NULL; + switch(orientation){ + case Parameters::North : + str = QString("north"); + break; + case Parameters::South : + str = QString("south"); + break; + case Parameters::East : + str = QString("east"); + break; + case Parameters::West : + str = QString("west"); + break; + } + + return str; +} + +int InterfaceItem::getIntOrientation(QString str) { + if(str == "west") return Parameters::West; + if(str == "east") return Parameters::East; + if(str == "south") return Parameters::South; + if(str == "north") return Parameters::North; + return -1; +} + + +/* connectWith() : + - modify all necessary attributes in the model to create a connection + between current InterfaceItem and iface. Note that the source and destination + are deduced from the direction (In, Out) and the type of the owner (funcitonal, group) + + CAUTION: No security checks are done. This method must be called only if canConnectWith has been called and returned true. + + NOTE : conditions so that this InterfaceItem can be connected with inter. + (i.e. current one can connect to inter OR inter can connect to current) + + Here are all the possible combinations, depending on the type of the + block/item and direction of the interface, which are : + GI/GB : a GroupItem referencing a GroupBlock (single solution for GI) + BI/FB : a BlockItem referencing a FunctionalBlock + BI/GB : a BlockItem referencing a GroupBlock + + For GI/GB: + - Input can connect with BI/FB or BI/GB Input + - Output can connect with BI/FB or BI/GB Output + + For BI/FB: + - Input can connect with: + GI/GB Input + BI/FB Output + BI/GB Output + - Output can connect with: + GI/GB Output + BI/FB Input + BI/GB Input + + For BI/GB: + - Input can connect with: + GI/GB Input + BI/FB Output + BI/GB Output + - Output can connect with: + GI/GB Output + BI/FB Input + BI/GB Input + + And whatever the case an InOut can only connect with an InOut + We note that: + - the IG does not allow the connect a GI/GB interface to an + interface of another GI/GB, thus the case is not considered above. + - BI/FB and BI/GB are the same. + - the cases where direction are the same only occur when + the 2 items are of different type (GI and BI) + - the cases where directions are different only occur when + the 2 are of both BlockItem + +*/ +bool InterfaceItem::connectWith(InterfaceItem *iface) { + ConnectedInterface* interThis = refInter; // the reference of this + ConnectedInterface* interOther = iface->refInter; // the reference of the other + ConnectedInterface* src = NULL, *dest = NULL; + + if(interThis->getDirection() == AbstractInterface::InOut && interOther->getDirection() == AbstractInterface::InOut){ + /* NOTE: InOut interfaces have both directions and thus are + connected from inter1 to inter2 AND inter2 to inter1 + Another effect is that a InOut can be connected to/from a single + InOut. + */ + if((interThis->getConnectedFrom() == NULL) && (interOther->getConnectedFrom() == NULL)) { + + interOther->connectFrom(interThis); + interOther->getConnectedTo().append(interThis); + interThis->connectFrom(interOther); + interThis->getConnectedTo().append(interOther); + + cout << "connecting 2 InOut"<< endl; + return true; + } + return false; + } + else if (interThis->getDirection() == interOther->getDirection()) { + + // cannot connect GI to GI or 2 BI of the same direction. + if ((getOwner()->isGroupItem()) && (iface->getOwner()->isGroupItem())) return false; + if ((getOwner()->isBoxItem()) && (iface->getOwner()->isBoxItem())) return false; + + if (interThis->getDirection() == AbstractInterface::Input) { // both are inputs + cout << "connecting GI to BI" << endl; + if(getOwner()->isGroupItem()) { + src = interThis; + dest = interOther; + } + else { + src = interOther; + dest = interThis; + } + } + else { // both are outputs + cout << "connecting BO to GO" << endl; + if(getOwner()->isGroupItem()){ + src = interOther; + dest = interThis; + } + else { + src = interThis; + dest = interOther; + } + } + } + else { + if ((getOwner()->isGroupItem()) || (iface->getOwner()->isGroupItem())) return false; + + cout << "connecting BO to BI" << endl; + if(interOther->getDirection() == AbstractInterface::Output) { + src = interOther; + dest = interThis; + } + else { + src = interThis; + dest = interOther; + } + } + + if(dest != NULL && src != NULL){ + // cannot overrive existing connectionFrom + if(dest->getConnectedFrom() == NULL) { + dest->connectFrom(src); + src->connectTo(dest); + return true; + } + else { + return false; + } + } + return false; +} + +void InterfaceItem::unconnectTo(InterfaceItem *iface) +{ + if(iface->refInter->getConnectedFrom() == refInter){ + iface->refInter->connectFrom(NULL); + } + if(iface->refInter->getConnectedTo().contains(refInter)){ + cout << "abnormal case while removing iface conn from " << qPrintable(name) << " to " << qPrintable(iface->name) << endl; + iface->refInter->removeConnectedTo(refInter); + } + if(refInter->getConnectedFrom() == iface->refInter) { + cout << "abnormal case while removing iface conn from " << qPrintable(name) << " to " << qPrintable(iface->name) << endl; + refInter->connectFrom(NULL); + } + if(refInter->getConnectedTo().contains(iface->refInter)){ + refInter->removeConnectedTo(iface->refInter); + } +} + +void InterfaceItem::updatePosition() +{ + if(orientation == Parameters::North || orientation == Parameters::South){ + position = positionRatio * owner->getWidth(); + } else { + position = positionRatio * owner->getHeight(); + } + setOriginPoint(); +} + +void InterfaceItem::addConnectionItem(ConnectionItem* item) { + connections.append(item); +} + +void InterfaceItem::removeConnectionItem(ConnectionItem* item) { + connections.removeOne(item); +} + +QDataStream &operator <<(QDataStream &out, InterfaceItem *i) { + +#ifdef DEBUG_INCLFUN + out.setVersion(QDataStream::Qt_5); + + QByteArray interfaceData; + QDataStream toWrite(&interfaceData, QIODevice::WriteOnly); + + toWrite << i->getId(); + toWrite << i->getName(); + toWrite << i->getPositionRatio(); + toWrite << i->getOrientation(); + + foreach(QGraphicsItem* item, i->params->getCurrentScene()->items()){ + if(item->data(0) == "connection"){ + ConnectionItem *conn = dynamic_cast(item); + if(conn->getFromInterfaceItem() == i || conn->getToInterfaceItem() == i){ + toWrite << conn->getId(); + cout << "id connection : " << conn->getId() << endl; + } + } + } + out << interfaceData; +#endif + return out; +} + +QDataStream &operator >>(QDataStream &in, InterfaceItem &i) { + +#ifdef DEBUG_INCLFUN + in.setVersion(QDataStream::Qt_5); + + int id, orientation; + double positionRatio; + QString name; + + in >> id; + in >> name; + in >> positionRatio; + in >> orientation; + + i.setId(id); + i.setName(name); + i.setPositionRatio(positionRatio); + i.setOrientation(orientation); + i.updatePosition(); +#endif + return in; +} diff --git a/InterfaceItem.h b/InterfaceItem.h new file mode 100644 index 0000000..ce65b9e --- /dev/null +++ b/InterfaceItem.h @@ -0,0 +1,88 @@ +#ifndef __INTERFACEITEM_H__ +#define __INTERFACEITEM_H__ + +#include +#include + +class Parameters; +class ConnectedInterface; +class AbstractBoxItem; +class ConnectionItem; + + +using namespace std; +using namespace Qt; + +class InterfaceItem { + +public: + + + + InterfaceItem(double _position, + int _orientation, + ConnectedInterface* _refInter, + AbstractBoxItem* _owner, + Parameters* _params); + InterfaceItem(); + QRectF boundingRect() const; + void paint(QPainter *painter); + + // getters + inline int getId() { return id; } + inline QString getName() { return name; } + inline double getPositionRatio() { return positionRatio; } + inline double getPosition() { return position; } + inline int getOrientation() { return orientation; } + inline AbstractBoxItem *getOwner() { return owner; } + inline int getNameWidth() { return nameWidth; } + inline int getNameHeight() { return nameHeight; } + QString getStrOrientation(); + static int getIntOrientation(QString str); + QPointF getEndPointInGroup(); + + // setters + void setOriginPoint(); + inline void setId(int id){ this->id = id; } + inline void setName(QString name){ this->name = name; } + inline void setPositionRatio(double ratio) { positionRatio = ratio; } + inline void setOrientation(int _orientation){ orientation = _orientation; } + + // others + void addConnectionItem(ConnectionItem* item); + void removeConnectionItem(ConnectionItem* item); + bool canConnectWith(InterfaceItem* iface); + bool connectWith(InterfaceItem* iface); + void unconnectTo(InterfaceItem *iface); + void updatePosition(); + + AbstractBoxItem* owner; + ConnectedInterface* refInter; + Parameters* params; + bool selected; + bool visible; + + static int counter; + + QList connections; // the connection items that are bounded to this interface item + + +private: + int id; + QString name; + double positionRatio; + int position; // position in pixels on the "orientation side" of the owner + int orientation; // north, south, east, west + QPointF originPoint; // the point where starts the drawing (along the box border) NB : in the owner coordinates + int nameWidth; // the graphical width of the name, computed from default font metrics + int nameHeight; // the graphical height of the name, computed from default font metrics + + friend QDataStream &operator<<(QDataStream &out, InterfaceItem *b); + friend QDataStream &operator>>(QDataStream &in, InterfaceItem &b); +}; + +/* +QDataStream & operator <<(QDataStream &out, InterfaceItem *b); +QDataStream & operator >>(QDataStream &in, InterfaceItem &b); +*/ +#endif diff --git a/InterfacePropertiesWindow.cpp b/InterfacePropertiesWindow.cpp new file mode 100644 index 0000000..2200bf6 --- /dev/null +++ b/InterfacePropertiesWindow.cpp @@ -0,0 +1,30 @@ +#include "InterfacePropertiesWindow.h" + +#include "ConnectedInterface.h" + +InterfacePropertiesWindow::InterfacePropertiesWindow(InterfaceItem *_inter, QWidget *parent) : + QWidget(parent) +{ + inter = _inter; + + layout = new QGridLayout; + + + layout->addWidget(new QLabel("Interface properties"), 0, 0); + layout->addWidget(new QLabel(" "), 1, 0); + + layout->addWidget(new QLabel("Name :"), 2, 0); + layout->addWidget(new QLabel(inter->getName()), 2, 1); + layout->addWidget(new QLabel("Width :"), 3, 0); + layout->addWidget(new QLabel(inter->refInter->getWidth()), 3, 1); + layout->addWidget(new QLabel("Direction :"), 4, 0); + layout->addWidget(new QLabel(inter->refInter->getDirectionString()), 4, 1); + layout->addWidget(new QLabel("Purpose :"), 5, 0); + layout->addWidget(new QLabel(inter->refInter->getPurposeString()), 5, 1); + layout->addWidget(new QLabel("Level :"), 6, 0); + layout->addWidget(new QLabel(inter->refInter->getLevelString()), 6, 1); + + this->setLayout(layout); + + show(); +} diff --git a/InterfacePropertiesWindow.h b/InterfacePropertiesWindow.h new file mode 100644 index 0000000..3b372a9 --- /dev/null +++ b/InterfacePropertiesWindow.h @@ -0,0 +1,22 @@ +#ifndef __INTERFACEPROPERTIESWINDOW_H__ +#define __INTERFACEPROPERTIESWINDOW_H__ + +#include + +#include "InterfaceItem.h" + +class InterfacePropertiesWindow : public QWidget +{ + Q_OBJECT +public: + explicit InterfacePropertiesWindow(InterfaceItem *_inter, QWidget *parent = 0); + +private: + QGridLayout *layout; + InterfaceItem *inter; + + + +}; + +#endif // INTERFACEPROPERTIESWINDOW_H diff --git a/MainWindow.cpp b/MainWindow.cpp new file mode 100644 index 0000000..01c23c1 --- /dev/null +++ b/MainWindow.cpp @@ -0,0 +1,399 @@ +#include "MainWindow.h" +#include "Dispatcher.h" +#include "Parameters.h" +#include "BlockLibraryWidget.h" +#include "GroupWidget.h" +#include "GroupScene.h" +#include "BlockWidget.h" +#include "AbstractBoxItem.h" +#include "Graph.h" +#include "GroupItem.h" +#include +#include +#include +#include + +MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) { + + versionMaj = VERSION_MAJ; + versionMin = VERSION_MIN; + revision = REVISION; + + // reading parameters + params = new Parameters(); + try { + params->loadBlastConfiguration("blastconfig.xml"); + + if (!QFileInfo::exists(params->refLib)) { + params->loadReferencesFromXml(); + int ret = QMessageBox::question(this,tr("Building references library"),tr("The reference block library does not exists.\n References have been read directly from the xml descriptions of blocks.\n It can be saved into a library in order to start application faster. "), QMessageBox::Ok | QMessageBox::Cancel, QMessageBox::Ok); + if (ret == QMessageBox::Ok) { + params->saveReferencesToLib(); + } + } + else { + params->loadReferencesFromLib(); + } + + if (!QFileInfo::exists(params->implLib)) { + params->loadImplementationsFromXml(); + int ret = QMessageBox::question(this,tr("Building implementations library"),tr("The block implementations library does not exists.\nImplementations have been read directly from the xml descriptions.\n It can be saved into a library in order to start application faster. "), QMessageBox::Ok | QMessageBox::Cancel, QMessageBox::Ok); + if (ret == QMessageBox::Ok) { + params->saveImplementationsToLib(); + } + } + else { + params->loadImplementationsFromLib(); + } + } + catch(Exception err) { + cerr << qPrintable(err.getDefaultMessage()) << endl; + cerr << "Aborting ..." << endl; + exit(1); + } + + cout << "all references and implementations are loaded" << endl; + + // create the menu, action, ... + dispatcher = new Dispatcher(params,this); + params->setDispatcher(dispatcher); + + // creating block library + library = new BlockLibraryWidget(dispatcher,params); + isCurrentProject = false; + + QLabel* labDefault = new QLabel("BLAST: BLock ASsembler Tool"); + stackedWidget = new QStackedWidget; + stackedWidget->addWidget(labDefault); + stackedWidget->setCurrentIndex(0); + this->setCentralWidget(stackedWidget); + this->setMinimumSize(800,600); + + createActions(); + createMenus(); + setWindowTitle("blast - top group"); + setFocusPolicy(Qt::StrongFocus); + setFocus(); + + readSettings(); + + initialize(); + +} + +MainWindow::~MainWindow() {} + +void MainWindow::initialize() { + + projectMenuEnb = 0; + + stackedWidget->setCurrentIndex(0); + enableProjectActions(true,PROJECT_NEW | PROJECT_OPEN, OP_RAZ); + + stackedWidget->setCurrentIndex(0); + + resize(500,300); +} + +void MainWindow::readSettings() { + QSettings settings; + settings.beginGroup("init"); + checkNewVersion = settings.value("check_new_version").toString(); + settings.endGroup(); + if (checkNewVersion.isEmpty()) { + checkNewVersion = "true"; + } + /* + cout << "test nouvelle version : " << qPrintable(checkNewVersion) << endl; + QUrl url("http://www.hrafnheim.fr/download/Cartomagic/carto_currentversion.txt"); + QNetworkRequest request(url); + manager = new QNetworkAccessManager(this); + connect(manager,SIGNAL(finished(QNetworkReply*)), this, SLOT(slotCheckNewVersion(QNetworkReply*))); + manager->get(request); + */ +} + +void MainWindow::writeSettings() { + QSettings settings; + settings.beginGroup("init"); + settings.setValue("check_new_version",checkNewVersion); + settings.endGroup(); +} + +void MainWindow::slotCheckNewVersion(QNetworkReply *reply) { + + if (reply->error() == QNetworkReply::NoError) { + QString txt = reply->readAll(); + QString currentVer = QString("%1.%2.%3").arg(versionMaj).arg(versionMin).arg(revision); + if (txt.contains(currentVer)) { + cout << "a jour" << endl; + } + else { + cout << "dernière version : " << qPrintable(txt) << ", version actuelle = " << qPrintable(currentVer) << endl; + } + } + else { + cout << "erreur = " << qPrintable(reply->errorString()) << endl; + } + reply->deleteLater(); +} + +void MainWindow::enableProjectActions(bool enbMenu, quint16 mask, quint8 op) { + if (enbMenu) { + projectMenu->setEnabled(true); + } + else { + projectMenu->setEnabled(false); + } + + if (op == OP_ADD) { + projectMenuEnb = projectMenuEnb | mask; + } + else if (op == OP_REM) { + projectMenuEnb = (projectMenuEnb | mask) ^ mask; + } + else if (op == OP_RAZ) { + projectMenuEnb = mask; + } + + + if (projectMenuEnb & PROJECT_NEW) { + newProject->setEnabled(true); + } + else { + newProject->setEnabled(false); + } + if (projectMenuEnb & PROJECT_OPEN) { + openProject->setEnabled(true); + } + else { + openProject->setEnabled(false); + } + if (projectMenuEnb & PROJECT_SAVE) { + saveProject->setEnabled(true); + } + else { + saveProject->setEnabled(false); + } + if (projectMenuEnb & PROJECT_SAVEAS) { + saveAsProject->setEnabled(true); + } + else { + saveAsProject->setEnabled(false); + } + if (projectMenuEnb & PROJECT_CLOSE) { + closeProject->setEnabled(true); + } + else { + closeProject->setEnabled(false); + } + if (projectMenuEnb & PROJECT_LIB) { + openLibrary->setEnabled(true); + } + else { + openLibrary->setEnabled(false); + } +} + +void MainWindow::createMenus(){ + + allMenuBar = menuBar(); + + projectMenu = allMenuBar->addMenu(tr("&Project")); + toolsMenu = allMenuBar->addMenu(tr("&Tools")); + + projectMenu->addAction(newProject); + projectMenu->addAction(openProject); + projectMenu->addAction(saveProject); + projectMenu->addAction(saveAsProject); + projectMenu->addAction(closeProject); + projectMenu->addAction(openLibrary); + + toolsMenu->addAction(newBlockWidgetAct); + toolsMenu->addAction(graphValidation); + +} + +void MainWindow::createActions() { + + newProject = new QAction(tr("&New project"), this); + newProject->setIcon(QPixmap::fromImage(QImage("icons/new.ico"))); + newProject->setStatusTip(tr("Create a new project")); + connect(newProject, SIGNAL(triggered()), this, SLOT(slotNewProject())); + + openProject = new QAction(tr("&Open project"), this); + openProject->setIcon(QPixmap::fromImage(QImage("icons/load.png"))); + openProject->setStatusTip(tr("Open an existing project")); + connect(openProject, SIGNAL(triggered()), this, SLOT(slotLoadProject())); + + saveProject = new QAction(tr("&Save project"), this); + saveProject->setIcon(QPixmap::fromImage(QImage("icons/save.png"))); + saveProject->setStatusTip(tr("Save the current project")); + connect(saveProject, SIGNAL(triggered()), this, SLOT(slotSaveProject())); + + saveAsProject = new QAction(tr("&Save project as..."), this); + saveAsProject->setIcon(QPixmap::fromImage(QImage("icons/save-as.png"))); + saveAsProject->setStatusTip(tr("Save the current project as...")); + connect(saveAsProject, SIGNAL(triggered()), this, SLOT(slotSaveAsProject())); + + closeProject = new QAction(tr("&Close project"), this); + closeProject->setIcon(QPixmap::fromImage(QImage("icons/close.png"))); + closeProject->setStatusTip(tr("Close the current project")); + connect(closeProject, SIGNAL(triggered()), this, SLOT(slotCloseProject())); + + openLibrary = new QAction(tr("Open block &Library"), this); + openLibrary->setIcon(QPixmap::fromImage(QImage("icons/add_block.png"))); + openLibrary->setStatusTip(tr("Open block library window")); + connect(openLibrary, SIGNAL(triggered()), this, SLOT(slotOpenBlockLibrary())); + + newBlockWidgetAct = new QAction(tr("&XML generator"), this); + newBlockWidgetAct->setIcon(QPixmap::fromImage(QImage("icons/new.ico"))); + newBlockWidgetAct->setStatusTip(tr("Create a new XML generator")); + connect(newBlockWidgetAct, SIGNAL(triggered()), this, SLOT(slotNewBlockWidget())); + + graphValidation = new QAction(tr("&graph validation"), this); + graphValidation->setIcon(QPixmap::fromImage(QImage("icons/new.ico"))); + graphValidation->setStatusTip(tr("validate the graph")); + connect(graphValidation, SIGNAL(triggered()), this, SLOT(slotGraphValidation())); + +} + +void MainWindow::save(QString absoluteFilename) { + params->save(absoluteFilename); +} + +void MainWindow::slotLoadProject(){ + + absoluteFilename = QFileDialog::getOpenFileName(0, "select a project file", "save/",tr("sauvegardes (*.xml)")); + + if(! absoluteFilename.isEmpty()){ + GroupWidget* topGroup = dispatcher->loadProject(absoluteFilename); + if (topGroup != NULL) { + addTopGroup(topGroup); + } + else { + QMessageBox msgBox; + msgBox.setText("Cannot open the project."); + msgBox.setInformativeText("Do you want to save your changes?"); + msgBox.setStandardButtons(QMessageBox::Cancel); + msgBox.setDefaultButton(QMessageBox::Cancel); + + int ret = msgBox.exec(); + } + } +} + + +void MainWindow::slotNewProject(){ + + enableProjectActions(true, PROJECT_CLOSE | PROJECT_SAVE | PROJECT_SAVEAS | PROJECT_LIB, OP_RAZ); + GroupWidget* topGroup = dispatcher->createTopScene(); + addTopGroup(topGroup); + +} + +void MainWindow::slotCloseProject(){ + + // removing the GroupWidget from stack + QWidget *widget = stackedWidget->widget(1); + stackedWidget->removeWidget(widget); + stackedWidget->setCurrentIndex(0); + + dispatcher->closeCurrentProject(); + + + isCurrentProject = false; + params->unsaveModif = false; + absoluteFilename = QString(); + + initialize(); +} + + +void MainWindow::slotNewBlockWidget() { + new BlockWidget(); +} + +void MainWindow::slotSaveProject(){ + if(absoluteFilename != QString()){ + save(absoluteFilename); + } else { + slotSaveAsProject(); + } +} + +void MainWindow::slotSaveAsProject(){ + if(isCurrentProject){ + QFileDialog dial(0, "Select a file", "save/"); + dial.setDefaultSuffix(".xml"); + dial.setAcceptMode(QFileDialog::AcceptSave); + if(dial.exec() == QFileDialog::AcceptSave){ + absoluteFilename = dial.selectedFiles().at(0); + if(absoluteFilename != QString()) + save(absoluteFilename); + } + } +} + +void MainWindow::slotOpenBlockLibrary() { + dispatcher->showBlocksLibrary(); +} + + +void MainWindow::slotGraphValidation() { + params->parametersValidation(); +} + +void MainWindow::addTopGroup(GroupWidget *_topGroup) { + topGroup = _topGroup; + stackedWidget->addWidget(topGroup); + stackedWidget->setCurrentIndex(1); +} + +void MainWindow::removeTopGroup() { + stackedWidget->removeWidget(topGroup); + topGroup->deleteLater(); + stackedWidget->setCurrentIndex(0); +} + +void MainWindow::closeEvent(QCloseEvent *event){ + if(isCurrentProject){ + QMessageBox msgBox; + msgBox.setText("The project has been modified."); + msgBox.setInformativeText("Do you want to save your changes?"); + msgBox.setStandardButtons(QMessageBox::Save | QMessageBox::Discard | QMessageBox::Cancel); + msgBox.setDefaultButton(QMessageBox::Save); + + int ret = msgBox.exec(); + + switch(ret) { + case QMessageBox::Save : + slotSaveProject(); + slotCloseProject(); + break; + case QMessageBox::Discard : + slotCloseProject(); + break; + } + event->ignore(); + } else { + exit(0); + } +} + +void MainWindow::mousePressEvent(QMouseEvent *e) { + + if (dispatcher->getCurrentGroup() != NULL) { + dispatcher->setCurrentGroupWidget(dispatcher->getCurrentGroup()); + } + QMainWindow::mousePressEvent(e); +} + +void MainWindow::focusInEvent(QFocusEvent *e) { +/* + if(params->currentWindow != this){ + params->setCurrentWindow(this); + changeConnectionMode(false); + } + */ +} diff --git a/MainWindow.h b/MainWindow.h new file mode 100644 index 0000000..ee51e4c --- /dev/null +++ b/MainWindow.h @@ -0,0 +1,128 @@ +#ifndef __MAINWINDOW_H__ +#define __MAINWINDOW_H__ + +#include + +#include +#include +#include +#include + +class Dispatcher; +class Parameters; +class BlockLibraryWidget; +class GroupWidget; +class BlockWidget; +class Graph; + +// versioning related +#define MAGIC_STRING "opentrace" +#define VERSION_MAJ (quint8)0 // major version code +#define VERSION_MIN (quint8)2 // minor version number +#define REVISION (quint8)1 // revision number of current version + + +// defines for menus +#define TRACE_MENU (quint8)1 + +// defines for actions +#define NONE_ACT (quint16)0 + +#define PROJECT_NEW (quint16)1 +#define PROJECT_OPEN (quint16)2 +#define PROJECT_SAVE (quint16)4 +#define PROJECT_SAVEAS (quint16)8 +#define PROJECT_CLOSE (quint16)16 +#define PROJECT_LIB (quint16)32 + +#define OP_ADD (quint8)0 +#define OP_REM (quint8)1 +#define OP_RAZ (quint8)2 + +using namespace std; +using namespace Qt; + + +class MainWindow : public QMainWindow { + + Q_OBJECT + +public: + explicit MainWindow(QWidget *parent = 0); + ~MainWindow(); + void initialize(); + + void addTopGroup(GroupWidget* _topGroup); + void removeTopGroup(); // called when closing project + + + inline BlockLibraryWidget *getLibrary(){return library;} + + +protected: + void closeEvent(QCloseEvent *); + void mousePressEvent(QMouseEvent *e); + void focusInEvent(QFocusEvent *e); + +private: + + GroupWidget* topGroup; + QStackedWidget *stackedWidget; + Dispatcher *dispatcher; + Parameters *params; + BlockLibraryWidget *library; + + bool isCurrentProject; + QString absoluteFilename; + + QString checkNewVersion; + + void save(QString absoluteFilename); + + void createActions(); + void createMenus(); + + void readSettings(); + void writeSettings(); + + /* Menus */ + QMenuBar *allMenuBar; + + QMenu* projectMenu; + quint16 projectMenuEnb; + QMenu* toolsMenu; + + QAction* newProject; + QAction* openProject; + QAction* saveProject; + QAction* saveAsProject; + QAction* closeProject; + QAction* openLibrary; + + QAction *newBlockWidgetAct; + QAction *graphValidation; + + + // versioning related + quint8 versionMaj; + quint8 versionMin; + quint8 revision; + +public slots: + void enableProjectActions(bool enbMenu, quint16 mask = 0, quint8 op = 0); // default : add nothing + +private slots: + void slotNewProject(); + void slotLoadProject(); + void slotSaveProject(); + void slotSaveAsProject(); + void slotCloseProject(); + void slotOpenBlockLibrary(); + + void slotNewBlockWidget(); + void slotGraphValidation(); + + void slotCheckNewVersion(QNetworkReply *reply); +}; + +#endif // MAINWINDOW_H diff --git a/Makefile.in b/Makefile.in new file mode 100644 index 0000000..e18672c --- /dev/null +++ b/Makefile.in @@ -0,0 +1,185 @@ +############################################################################# +# Makefile for building: @@APPNAME@@ +############################################################################# + +####### VERSION (modified by the configure script) ############ +APPNAME=@@APPNAME@@ + +####### VERSION (modified by the configure script) ############ +VERSION=@@APPVER@@ + +####### OS (modified by the configure script) ############ +OS=@@OS@@ +ARCH=@@ARCH@@ + +####### Qt (modified by the configure script) ############ +QTVER=@@QTVER@@ +QTPATH=@@QTPATH@@ + +####### PATHES ############ +BUILDDIR=build +DISTDIR=dist +BUILDPATH=$(BUILDDIR)/$(OS)$(ARCH) +DISTPATH=$(DISTDIR)/$(OS)$(ARCH)/$(APPNAME)-$(VERSION) +ARDIR=archive +ARPATH=$(ARDIR)/$(APPNAME)/v$(VERSION) + +####### COMPILERS ############ +CC-lin32 = gcc +CXX-lin32 = g++ +CC-lin64 = gcc +CXX-lin64 = g++ +CC-win32 = i686-w64-mingw32-gcc-win32 +CXX-win32 = i686-w64-mingw32-g++-win32 +WINDRES-win32 = i686-w64-mingw32-windres +CC-win64 = x86_64-w64-mingw32-gcc-win32 +CXX-win64 = x86_64-w64-mingw32-g++-win32 +WINDRES-win64 = x86_64-w64-mingw32-windres + +CC = $(CC-$(OS)$(ARCH)) +CXX = $(CXX-$(OS)$(ARCH)) +WINDRES = $(WINDRES-$(OS)$(ARCH)) + +DEFINES-lin32 = -DDEBUG -DQT_NO_DEBUG -DQT_XML_LIB -DQT_GUI_LIB -DQT_CORE_LIB -DQT_SHARED -D@@APPDEF@@_LIN +DEFINES-lin64 = -DDEBUG -DQT_NO_DEBUG -DQT_XML_LIB -DQT_GUI_LIB -DQT_CORE_LIB -DQT_SHARED -D@@APPDEF@@_LIN +DEFINES-win32 = -DUNICODE -DQT_LARGEFILE_SUPPORT -DQT_DLL -DQT_NO_DEBUG -DQT_HAVE_MMX -DQT_HAVE_3DNOW -DQT_HAVE_SSE -DQT_HAVE_MMXEXT -DQT_HAVE_SSE2 -DQT_THREAD_SUPPORT -DQT_NEEDS_QMAIN -DQT_AXCONTAINER_LIB -DQT_PRINTSUPPORT_LIB -DQT_AXBASE_LIB -DQT_WIDGETS_LIB -DQT_GUI_LIB -DQT_CORE_LIB -DQT_XML_LIB -D@@APPDEF@@_WIN + +DEFINES = $(DEFINES-$(OS)$(ARCH)) + +CFLAGS-lin32 = @@DEBUG@@ -pipe -O2 -Wall -fPIC -D_REENTRANT $(DEFINES) +CFLAGS-lin64 = @@DEBUG@@ -pipe -O2 -Wall -fPIC -D_REENTRANT $(DEFINES) +CXXFLAGS-lin32 = @@DEBUG@@ -pipe -O2 -Wall -fPIC -D_REENTRANT $(DEFINES) +CXXFLAGS-lin64 = @@DEBUG@@ -pipe -O2 -Wall -fPIC -D_REENTRANT $(DEFINES) +CFLAGS-win32 = @@DEBUG@@ -pipe -fno-keep-inline-dllexport -O2 -Wall -Wextra $(DEFINES) +CXXFLAGS-win32 = @@DEBUG@@ -pipe -fno-keep-inline-dllexport -O2 -frtti -Wall -Wextra -fexceptions -mthreads $(DEFINES) + +CFLAGS = $(CFLAGS-$(OS)$(ARCH)) +CXXFLAGS = $(CXXFLAGS-$(OS)$(ARCH)) + +INCPATH-lin64-qt4 = -I/usr/share/qt4/mkspecs/linux-g++ -I$(QTPATH)/QtCore -I$(QTPATH)/QtGui -I$(QTPATH)/QtNetwork -I$(QTPATH)/QtXml -I$(QTPATH)/QtXmlPatterns -I$(QTPATH) +INCPATH-lin64-qt5 = -I$(QTPATH)/mkspecs/linux-g++-64 -I$(QTPATH) -I$(QTPATH)/QtWidgets -I$(QTPATH)/QtCore -I$(QTPATH)/QtGui -I$(QTPATH)/QtPrintSupport -I$(QTPATH)/QtNetwork -I$(QTPATH)/QtXml -I$(QTPATH)/QtXmlPatterns +INCPATH-win32-qt5 = -I$(QTPATH)/mkspecs/win32-g++ -I$(QTPATH)/include -I$(QTPATH)/include/ActiveQt -I$(QTPATH)/include/QtWidgets -I$(QTPATH)/include/QtGui -I$(QTPATH)/include/QtCore -I$(QTPATH)/include/QtXml -I$(QTPATH)/include/QtXmlPatterns -I$(QTPATH)/include/QtPrintSupport + +INCPATH = $(INCPATH-$(OS)$(ARCH)-qt$(QTVER)) -I. + +################## LINKER ##################### +LINK-lin32 = g++ +LINK-lin64 = g++ +LINK-win32 = i686-w64-mingw32-g++-win32 +LINK-win64 = x86_64-w64-mingw32-g++-win32 + +LINK = $(LINK-$(OS)$(ARCH)) + +LFLAGS-lin32 = -m32 -Wl,-O1 +LFLAGS-lin64 = -m64 -Wl,-O1 +LFLAGS-win32 = -Wl,-s -mthreads -Wl,-subsystem,console -mthreads -static-libgcc -static-libstdc++ +LFLAGS-win64 = -Wl,-s -mthreads -Wl,-subsystem,console -mthreads -static-libgcc -static-libstdc++ + +LFLAGS = $(LFLAGS-$(OS)$(ARCH)) + +LIBS-lin64-qt4 = -lQtCore -lQtGui -lQtXml -lQtXmlPatterns -lpthread -lm +LIBS-lin64-qt5 = -lQt5Core -lQt5Gui -lQt5Xml -lQt5XmlPatterns -lQt5Widgets -lQt5PrintSupport -lQt5Network -lpthread -lm +LIBS-win32-qt5 = -lmingw32 -L$(QTPATH)/lib -lqtmain -lQt5AxContainer -lQt5PrintSupport -lQt5AxBase -lglu32 -lopengl32 -lole32 -loleaut32 -luser32 -lgdi32 -ladvapi32 -luuid -lQt5Widgets -lQt5Gui -lQt5Core -lQt5Xml -lQt5XmlPatterns -lQt5Network -lm -lws2_32 + +LIBS = $(LIBS-$(OS)$(ARCH)-qt$(QTVER)) + +################## OBJECTS ##################### +include object-files.txt + +OBJ-lin = $(COMMON-OBJ) +OBJ-win = $(COMMON-OBJ) $(BUILDPATH)/$(APPNAME)_res.o + +OBJ = $(OBJ-$(OS)) + +################## EXECUTBALE #################### +EXENAME-lin = $(APPNAME) +EXENAME-win = $(APPNAME).exe + +EXENAME = $(EXENAME-$(OS)) + +all: $(BUILDPATH)/$(EXENAME) + +$(BUILDPATH)/$(EXENAME) : $(OBJ) + $(LINK) $(LFLAGS) -o $@ $^ $(LIBS) + cp $(BUILDPATH)/$(EXENAME) . + +$(BUILDPATH)/$(APPNAME)_res.o: $(APPNAME).rc + $(WINDRES) -i $(APPNAME).rc -o $(BUILDPATH)/$(APPNAME)_res.o --include-dir=. $(DEFINES) + +################## DISTRIBUTION ARCHIVE ##################### +DISTNAME = dist-$(OS) + +dist : $(DISTNAME) + +dist-lin: $(BUILDPATH)/$(APPNAME) + @echo -n creating binary distribution archive for Linux ... + @-rm -rf $(DISTPATH) + @-mkdir -p $(DISTPATH) + @-mkdir -p $(DISTPATH)/locales + @cp $(BUILDPATH)/$(APPNAME) $(DISTPATH) + @if ls $(APPNAME)_*.qm 2>/dev/null 1>&2; then for loc in $(APPNAME)_*.qm; do cp $$loc $(DISTPATH)/locales; done; fi + @cp install.sh $(DISTPATH) + @if [ -d examples ]; then cp -r examples $(DISTPATH); fi + @cd $(DISTPATH); cd ..; tar zcf $(APPNAME)-$(VERSION)_$(OS)$(ARCH).tgz $(APPNAME)-$(VERSION); rm -rf $(APPNAME)-$(VERSION) + @echo done; echo result is in $(DISTDIR)/$(OS)$(ARCH)/$(APPNAME)-$(VERSION)_$(OS)$(ARCH).tgz + +dist-win: $(BUILDPATH)/$(EXENAME) + @echo -n creating binary distribution archive for Windows ... + @rm -rf $(DISTPATH) + @mkdir -p $(DISTPATH) + @cp $(BUILDPATH)/$(APPNAME).exe $(DISTPATH) + @cp -r lib/$(OS)$(ARCH)/* $(DISTPATH) + @if ls $(APPNAME)_*.qm 2>/dev/null 1>&2; then for loc in $(APPNAME)_*.qm; do cp $$loc $(DISTPATH); done; fi + @if [ -d examples ]; then cp -r examples $(DISTPATH); fi + @cd $(DISTPATH); cd ..; zip -r $(APPNAME)-$(VERSION)_$(OS)$(ARCH).zip $(APPNAME)-$(VERSION)/* ; rm -rf $(APPNAME)-$(VERSION) + @echo done; echo result is in $(DISTDIR)/$(OS)$(ARCH)/$(APPNAME)-$(VERSION)_$(OS)$(ARCH).zip + +src-dist: + @echo -n creating source archive ... + @rm -rf $(ARPATH) + @mkdir -p $(ARPATH) + @cp *.cpp $(ARPATH) + @cp *.h $(ARPATH) +# copying various files: translations (.ts), locales (.qm), resources (.qrc), xml related (.xml,.xsd), config. files (.conf) ... + @if ls $(APPNAME)_*.ts 2>/dev/null 1>&2; then for loc in $(APPNAME)_*.ts; do cp $$loc $(ARPATH); done; fi + @if ls $(APPNAME)_*.qm 2>/dev/null 1>&2; then for loc in $(APPNAME)_*.qm; do cp $$loc $(ARPATH); done; fi + @if ls *.conf 2>/dev/null 1>&2; then for loc in *.conf; do cp $$loc $(ARPATH); done; fi + @if ls *.xml 2>/dev/null 1>&2; then for loc in *.xml; do cp $$loc $(ARPATH); done; fi + @if ls *.xsd 2>/dev/null 1>&2; then for loc in *.xsd; do cp $$loc $(ARPATH); done; fi + @if [ -f $(APPNAME).qrc ]; then cp $(APPNAME).qrc $(ARPATH); fi + @if [ -d icons ]; then cp -r icons $(ARPATH); fi + @if [ -d examples ]; then cp -r examples $(ARPATH); fi + @cp -r lib $(ARPATH) + @cp install.sh.in $(ARPATH) + @cp Makefile.in $(ARPATH) + @cp object-files.txt $(ARPATH) + @cp configure $(ARPATH) + @if [ -f $(APPNAME).rc ]; then cp $(APPNAME).rc $(ARPATH); fi + @if [ -f $(APPNAME).ico ]; then cp $(APPNAME).ico $(ARPATH); fi + @cp $(APPNAME).creator $(ARPATH) + @if [ -f $(APPNAME).creator.user ]; then cp $(APPNAME).creator.user $(ARPATH); fi + @cp $(APPNAME).files $(ARPATH) + @cp $(APPNAME).includes $(ARPATH) + @if [ -f $(APPNAME).config ]; then cp $(APPNAME).config $(ARPATH); fi + @cd $(ARPATH); cd ../..; tar zcf $(APPNAME)-$(VERSION)-src.tgz $(APPNAME); rm -rf $(APPNAME) + @echo done; echo result is in $(ARDIR)/$(APPNAME)-$(VERSION)-src.zip +clean: + rm -f $(OBJ) + rm -f *~ + rm -f moc_*.cpp + rm -f rcc_*.cpp + rm -f $(BUILDPATH)/$(EXENAME) + rm -f $(EXENAME) + +### NEW RULES ### +$(BUILDPATH)/%.o : %.cpp + $(CXX) -c $(CXXFLAGS) $(INCPATH) -o $@ $< + +moc_%.cpp : %.h + moc -qt=$(QTVER) $(DEFINES) $(INCPATH) $< -o $@ + +rcc_%.cpp : %.qrc + rcc -qt=$(QTVER) $< -o $@ -name $* + +### DEPENDENCIES OF EACH SOURCE FILE (auto-added by configure) ### + diff --git a/Parameters.cpp b/Parameters.cpp new file mode 100644 index 0000000..c991e5f --- /dev/null +++ b/Parameters.cpp @@ -0,0 +1,1220 @@ +#include "Parameters.h" + +#include "Dispatcher.h" +#include "BlockLibraryTree.h" + +#include "BlockCategory.h" + +#include "GroupWidget.h" +#include "GroupScene.h" +#include "GroupItem.h" +#include "BoxItem.h" +#include "InterfaceItem.h" +#include "ConnectionItem.h" + +#include "Graph.h" +#include "ReferenceBlock.h" +#include "GroupBlock.h" +#include "FunctionalBlock.h" +#include "ReferenceInterface.h" +#include "GroupInterface.h" +#include "FunctionalInterface.h" + +#include "Exception.h" +#include "BlocksToConfigureWidget.h" + +Parameters::Parameters() { + categoryTree = NULL; + arrowWidth = 5; + arrowHeight = 10; + arrowLineLength = 10; + + defaultBlockWidth = 100; + defaultBlockHeight = 100; + defaultBlockFont = QFont("Arial"); + defaultBlockFontSize = 12; + + setArrowPathes(); + + sceneMode = MODE_EDITION; // default mode + editState = Parameters::EditNoOperation; + + unsaveModif = false; + isRstClkShown = false; + + projectPath = QDir::currentPath(); +} + +Parameters::~Parameters() { + clear(); +} + +void Parameters::clear() { + delete categoryTree; + QListIterator iter(availableBlocks); + while(iter.hasNext()) { + ReferenceBlock* item = iter.next(); + delete item; + } + availableBlocks.clear(); + refPathes.clear(); +} + +Graph* Parameters::createGraph() { + graph = new Graph(); + return graph; +} + +void Parameters::destroyGraph() { + delete graph; +} + +GroupBlock* Parameters::addGroupBlock() { + GroupBlock* parent = AB_TO_GRP(currentScene->getGroupItem()->getRefBlock()); + GroupBlock* newOne = graph->createChildBlock(parent); + return newOne; +} + +FunctionalBlock* Parameters::addFunctionalBlock(int idCategory, int idBlock) { + + BlockCategory* blockCat = categoryTree->searchCategory(idCategory); + + if (blockCat == NULL) return NULL; + GroupBlock* group = AB_TO_GRP(currentScene->getGroupItem()->getRefBlock()); + ReferenceBlock* ref = blockCat->getBlock(idBlock); + if (ref == NULL) return NULL; + + FunctionalBlock* newOne = graph->addFunctionalBlock(group, ref); + unsaveModif = true; + + return newOne; +} + +FunctionalBlock* Parameters::duplicateFunctionalBlock(FunctionalBlock *block) { + + ReferenceBlock* ref = block->getReference(); + GroupBlock* group = AB_TO_GRP(block->getParent()); + + // adding to the group + FunctionalBlock* newBlock = new FunctionalBlock(group,ref); + newBlock->populate(); + group->addBlock(newBlock); + + return newBlock; +} + +void Parameters::validateXmlFile(const QString& xmlFileName, const QString& xsdFileName, XmlFileType fileType) throw(Exception) { + // opening configFile + QFile xmlFile(xmlFileName); + if (!xmlFile.open(QIODevice::ReadOnly)) { + if (fileType == Configuration) { + throw(Exception(CONFIGFILE_NOACCESS)); + } + else if (fileType == Reference) { + throw(Exception(BLOCKFILE_NOACCESS)); + } + else if (fileType == Implementation) { + throw(Exception(IMPLFILE_NOACCESS)); + } + else if (fileType == Project) { + throw(Exception(PROJECTFILE_NOACCESS)); + } + } + + QFile xsdFile(xsdFileName); + if (!xsdFile.open(QIODevice::ReadOnly)) { + xsdFile.close(); + if (fileType == Configuration) { + throw(Exception(CONFIGFILE_NOACCESS)); + } + else if (fileType == Reference) { + throw(Exception(BLOCKFILE_NOACCESS)); + } + else if (fileType == Implementation) { + throw(Exception(IMPLFILE_NOACCESS)); + } + else if (fileType == Project) { + throw(Exception(PROJECTFILE_NOACCESS)); + } + } + + if(validate(xmlFile,xsdFile) == false) { + xmlFile.close(); + xsdFile.close(); + if (fileType == Configuration) { + throw(Exception(CONFIGFILE_CORRUPTED)); + } + else if (fileType == Reference) { + throw(Exception(BLOCKFILE_CORRUPTED)); + } + else if (fileType == Implementation) { + throw(Exception(IMPLFILE_CORRUPTED)); + } + else if (fileType == Project) { + throw(Exception(PROJECTFILE_CORRUPTED)); + } + } + xmlFile.close(); + xsdFile.close(); +} + +bool Parameters::validate(QFile& fileXml, QFile& fileSchema) { + + // 2 files are supposed to be already opened + QXmlSchema schema; + + if(! schema.load(&fileSchema)){ + cout << "invalid schema" << endl; + return false; + } + + QXmlSchemaValidator validator(schema); + + if(! validator.validate(&fileXml)){ + cout << "invalid xml" << endl; + return false; + } + return true; +} + +QDomElement Parameters::openProjectFile(const QString& projectFileName) throw(Exception) { + + try { + validateXmlFile(projectFileName,"projectfile.xsd",Project); + } + catch(Exception err) { + throw(err); + } + + QFile projectFile(projectFileName); + QDomDocument doc("projectFile"); + + if(!projectFile.open(QIODevice::ReadOnly)) { + throw(Exception(PROJECTFILE_NOACCESS)); + } + if(!doc.setContent(&projectFile)) { + projectFile.close(); + throw(Exception(PROJECTFILE_CORRUPTED)); + } + projectFile.close(); + + QDomElement root = doc.documentElement(); + + return root; +} + +void Parameters::loadProject(QDomElement root) { + +#ifdef DEBUG_INCLFUN + + bool ok = false; + GroupWidget* groupWidget = NULL; + GroupItem *groupItem = NULL; + GroupBlock *groupBlock = NULL; + + /********************************************************** + 1 : getting scene and creating associated group widgets + ***********************************************************/ + QDomNodeList scenesNodes = root.elementsByTagName("scene"); + + for(int i=0; icreateTopScene(); + topScene->setId(idScene); + groupItem = topScene->getGroupItem(); + groupBlock = AB_TO_GRP(groupItem->getRefBlock()); + cout << "top group added to scene n°" << idScene << endl; + } + else { + GroupScene* scene = searchSceneById(idUpperScene, topScene); + GroupWidget* parent = scene->getGroupWindow(); + groupWidget = dispatcher->createChildScene(parent); + groupItem = groupWidget->getScene()->getGroupItem(); + groupBlock = AB_TO_GRP(groupItem->getRefBlock()); + } + /********************************************************** + 1.1 : getting the group item + ***********************************************************/ + QDomElement groupItemNode = currentSceneNode.firstChildElement("group_item"); + + int id = groupItemNode.attribute("id","none").toInt(&ok); + if(!ok) throw(Exception(PROJECTFILE_CORRUPTED)); + + QString name = groupItemNode.attribute("name","none"); + if(name == "none") throw(Exception(PROJECTFILE_CORRUPTED)); + + QStringList positionStr = groupItemNode.attribute("position","none").split(","); + if(positionStr.length() != 2) throw(Exception(PROJECTFILE_CORRUPTED)); + int posX = positionStr.at(0).toInt(&ok); + if(!ok) throw(Exception(PROJECTFILE_CORRUPTED)); + int posY = positionStr.at(1).toInt(&ok); + if(!ok) throw(Exception(PROJECTFILE_CORRUPTED)); + + QStringList dimensionStr = groupItemNode.attribute("dimension","none").split(","); + if(dimensionStr.length() != 2) throw(Exception(PROJECTFILE_CORRUPTED)); + int dimX = dimensionStr.at(0).toInt(&ok); + if(!ok) throw(Exception(PROJECTFILE_CORRUPTED)); + int dimY = dimensionStr.at(1).toInt(&ok); + if(!ok) throw(Exception(PROJECTFILE_CORRUPTED)); + + groupItem->setId(id); + groupItem->setPos(posX,posY); + groupItem->setDimension(dimX,dimY); + groupBlock->setName(name); + + if (idUpperScene != -1) { + groupWidget->setWindowTitle(groupBlock->getName()); + groupWidget->show(); + } + cout << "group info : \n-id : " << id << "\n-pos : " << posX << ", " << posY << "\n-dim : " << dimX << ", " << dimY << "\n-name : " << name.toStdString() << endl; + + QDomNodeList interfaces = groupItemNode.elementsByTagName("group_iface"); + for(int j=0; jsetId(id); + + groupBlock->addInterface(groupInterface); + groupItem->addInterface(interfaceItem); + cout << "interface add to " << groupBlock->getName().toStdString() << endl; + } + } + + cout << "groupItems loaded and windows created succefully!" << endl; + + + for(int i=0; igetGroupItem()->getRefBlock(),reference); + functionalBlock->setName(name); + + ((GroupBlock*)currentScene->getGroupItem()->getRefBlock())->addBlock(functionalBlock); + + + BlockItem *blockItem = new BlockItem(currentScene->getGroupItem(),functionalBlock,dispatcher,this); + blockItem->setPos(posX,posY); + blockItem->setDimension(dimX,dimY); + blockItem->setId(id); + ((GroupItem*)currentScene->getGroupItem())->addBlockItem(blockItem); + currentScene->addItem(blockItem); + currentScene->addBlockItem(blockItem); + + QDomNodeList blockParamNodes = currentFBNode.elementsByTagName("bif_parameter"); + + for(int i=0; isetName(name); + blockParam->setValue(value); + blockParam->setType(type); + if(context == "constant") blockParam->setContext(BlockParameter::Constant); + if(context == "user") blockParam->setContext(BlockParameter::User); + if(context == "generic") blockParam->setContext(BlockParameter::Generic); + if(context == "wb") blockParam->setContext(BlockParameter::Wishbone); + if(context == "port") blockParam->setContext(BlockParameter::Port); + + functionalBlock->addParameter(blockParam); + } + + + QDomNodeList interfaceNodes = currentFBNode.elementsByTagName("bif_iface"); + + for(int i=0; igetIfaceFromName(refName); + FunctionalInterface *functionalInterface = new FunctionalInterface(functionalBlock,refInter); + functionalBlock->addInterface(functionalInterface); + functionalInterface->setName(refName); + + InterfaceItem *interfaceItem = new InterfaceItem(position,orientation,functionalInterface,blockItem,this); + interfaceItem->setId(id); + interfaceItem->setName(name); + + blockItem->addInterface(interfaceItem); + + } + } + } + cout << "functionalBlocks loaded and created succefully!" << endl; + + + for(int i=0; igetRefBlock(), dispatcher, this); + blockItem->setChildGroupItem(insideGroup); + blockItem->setId(id); + blockItem->setPos(posX,posY); + blockItem->setDimension(dimX,dimY); + + ((GroupItem*)currentScene->getGroupItem())->addBlockItem(blockItem); + currentScene->addItem(blockItem); + currentScene->addBlockItem(blockItem); + + QDomNodeList interfaceNodes = currentBiGroup.elementsByTagName("big_iface"); + + for(int k=0; ksearchInterfaceByName(refName)->refInter; + InterfaceItem *ifaceItem = new InterfaceItem(position, orientation, refInter, blockItem, this); + ifaceItem->setId(id); + blockItem->addInterface(ifaceItem); + } + } + } + cout << "blockItems \"group\" loaded and created succefully!" << endl; + + + for(int i=0; isetUpperItem(upperItem); + } + } + + QDomNodeList connectionNodes = root.elementsByTagName("connection"); + + for(int i=0; iconnect(iface1,iface2); + } else { + cout << "interfaces not found, connect canceled!" << endl; + } + } + +#endif +} + +void Parameters::loadBlastConfiguration(QString confFile) throw(Exception) { + + try { + validateXmlFile("blastconfig.xml", "blastconfig.xsd",Configuration); + } + catch(Exception err) { + throw(err); + } + + bool ok; + // opening configFile + QFile configFile(confFile); + // reading in into QDomDocument + QDomDocument document("configFile"); + + if (!configFile.open(QIODevice::ReadOnly)) { + throw(Exception(CONFIGFILE_NOACCESS)); + } + if (!document.setContent(&configFile)) { + configFile.close(); + throw(Exception(CONFIGFILE_NOACCESS)); + } + configFile.close(); + + //Get the root element + QDomElement config = document.documentElement(); + + QDomElement eltCat = config.firstChildElement("categories"); + try { + categoryTree = new BlockLibraryTree(); + categoryTree->load(eltCat); + } + catch(Exception err) { + throw(err); + } + + QDomElement eltReferences = eltCat.nextSiblingElement("references"); + refLib = eltReferences.attribute("lib_file","none"); + cout << "references lib : " << qPrintable(refLib) << endl; + int nbPathes; + QString nbPathesStr; + nbPathesStr = eltReferences.attribute("nb","none"); + nbPathes = nbPathesStr.toInt(&ok); + QDomNodeList listBlockDir = eltReferences.elementsByTagName("reference_lib"); + if ((!ok) || (nbPathes != listBlockDir.size())) throw(Exception(CONFIGFILE_CORRUPTED)); + + for(int i=0;i + // for each child element, initialize the associated attributes of Parameters + + QDomElement eltDefaults = eltImpl.nextSiblingElement("defaults"); + + QDomElement eltBlocks = eltDefaults.firstChildElement("blocks"); + QString attributeStr = eltBlocks.attribute("width", "none"); + defaultBlockWidth = attributeStr.toInt(&ok); + if (!ok || defaultBlockWidth < 1) throw(Exception(CONFIGFILE_CORRUPTED)); + attributeStr = eltBlocks.attribute("height", "none"); + defaultBlockHeight = attributeStr.toInt(&ok); + if (!ok || defaultBlockHeight < 1) throw(Exception(CONFIGFILE_CORRUPTED)); + attributeStr = eltBlocks.attribute("font_size", "none"); + defaultBlockFontSize = attributeStr.toFloat(&ok); + if (!ok || defaultBlockFontSize < 1) throw(Exception(CONFIGFILE_CORRUPTED)); + attributeStr = eltBlocks.attribute("font", "none"); + if (attributeStr == "none") throw(Exception(CONFIGFILE_CORRUPTED)); + defaultBlockFontName = attributeStr; + defaultBlockFont = QFont(defaultBlockFontName, defaultBlockFontSize); + + QDomElement eltInterfaces = eltBlocks.nextSiblingElement("interfaces"); + attributeStr = eltInterfaces.attribute("width", "none"); + arrowWidth = attributeStr.toInt(&ok); + if (!ok || arrowWidth < 1) throw(Exception(CONFIGFILE_CORRUPTED)); + attributeStr = eltInterfaces.attribute("height", "none"); + arrowHeight = attributeStr.toInt(&ok); + if (!ok || arrowHeight < 1) throw(Exception(CONFIGFILE_CORRUPTED)); + attributeStr = eltInterfaces.attribute("linelength", "none"); + arrowLineLength = attributeStr.toInt(&ok); + if (!ok || arrowLineLength < 1) throw(Exception(CONFIGFILE_CORRUPTED)); + attributeStr = eltInterfaces.attribute("font_size", "none"); + defaultIfaceFontSize = attributeStr.toFloat(&ok); + if (!ok || defaultIfaceFontSize < 1) throw(Exception(CONFIGFILE_CORRUPTED)); + attributeStr = eltInterfaces.attribute("font", "none"); + if (attributeStr == "none") throw(Exception(CONFIGFILE_CORRUPTED)); + defaultIfaceFontName = attributeStr; + defaultIfaceFont = QFont(defaultIfaceFontName, defaultIfaceFontSize); + + QDomElement eltConnections = eltInterfaces.nextSiblingElement("connections"); + attributeStr = eltConnections.attribute("gaplength", "none"); + connGapLength = attributeStr.toInt(&ok); + if (!ok || connGapLength < 1) throw(Exception(CONFIGFILE_CORRUPTED)); +} + +void Parameters::loadReferencesFromXml() throw(Exception) { + cout << "load references from xml" << endl; + for(int i=0;i")) { + blockXML.close(); + continue; + } + + blockXML.close(); + try { + validateXmlFile(fileName,"block.xsd",Reference); + } + catch(Exception err) { + throw(err); + } + + // reading in into QDomDocument + QDomDocument document ("FileXML"); + if (!blockXML.open(QIODevice::ReadOnly)) { + throw(Exception(BLOCKFILE_NOACCESS)); + } + if (!document.setContent(&blockXML)) { + blockXML.close(); + throw(Exception(BLOCKFILE_NOACCESS)); + } + blockXML.close(); + + QDomElement blockRoot = document.documentElement(); + QString name = blockRoot.tagName(); + if (name == "block") { + + cout << "xml:" << fileName.toStdString() << endl; + ReferenceBlock* b = new ReferenceBlock(fileName); + try { + b->load(blockRoot); + b->setHashMd5(); + } + catch(int err) { + throw(err); + } + cout << "xml:" << b->getXmlFile().toStdString() << endl; + + availableBlocks.append(b); + foreach (int id,b->getCategories()) { + cout << "ajout du bloc dans cat n° : " << id << endl; + BlockCategory* cat = categoryTree->searchCategory(id); + cat->blocks.append(b); + } + } + } + } +} + +void Parameters::loadReferencesFromLib() throw(Exception) { + + cout << "loading references from lib" << endl; + + // removing blocks from category tree if they exist + categoryTree->clearBlocks(); + // deleting existings blocks + ReferenceBlock* b = NULL; + for(int i=0;i> size; + + int nbBlocks; + in >> nbBlocks; + for(int i=0;i> *b; + availableBlocks.append(b); + foreach (int id,b->getCategories()) { + BlockCategory* cat = categoryTree->searchCategory(id); + cat->blocks.append(b); + } + } + libFile.close(); +} + +void Parameters::saveReferencesToLib() throw(Exception) { + + cout << "saving blocks in " << qPrintable(refLib) << endl; + QFile libFile(refLib); + if (!libFile.open(QIODevice::WriteOnly)) { + throw(Exception(BLOCKFILE_NOACCESS)); + } + QDataStream out(&libFile); + + out.setVersion(QDataStream::Qt_5_0); + + QByteArray blockData; + QDataStream toWrite(&blockData, QIODevice::WriteOnly); + + toWrite << availableBlocks.size(); + for(int i=0;iaddImplementation(impl); + impl->setReference(ref); + cout << "OK" << endl; + } + } +} + +void Parameters::loadImplementationsFromLib() throw(Exception) { + + cout << "loading implementations from lib" << endl; + + BlockImplementation* impl = NULL; + ReferenceBlock* ref = NULL; + for(int i=0;i> size; + + int nbImpls; + in >> nbImpls; + for(int i=0;i> *impl; + availableImplementations.append(impl); + QString refMd5 = impl->getReferenceMd5(); + QString refXml = impl->getReferenceXml(); + ref = NULL; + if (! refMd5.isEmpty()) { + ref = searchBlockByMd5(refMd5); + } + if (ref == NULL) { + ref = searchBlockByXml(refXml); + } + if (ref == NULL) { + cout << "Cannot find a reference block for impl :" << qPrintable(impl->getXmlFile()) << endl; + } + ref->addImplementation(impl); + impl->setReference(ref); + } + libFile.close(); +} + +void Parameters::saveImplementationsToLib() throw(Exception) { + + cout << "saving implementations in " << qPrintable(implLib) << endl; + QFile libFile(implLib); + if (!libFile.open(QIODevice::WriteOnly)) { + throw(Exception(IMPLFILE_NOACCESS)); + } + QDataStream out(&libFile); + + out.setVersion(QDataStream::Qt_5_0); + + QByteArray blockData; + QDataStream toWrite(&blockData, QIODevice::WriteOnly); + + toWrite << availableImplementations.size(); + for(int i=0;igetCategories()) { + cout << "ajout du bloc dans cat n° : " << id << endl; + BlockCategory* cat = categoryTree->searchCategory(id); + cat->blocks.append(block); + } +} + +void Parameters::parametersValidation() { + QList blocksToConfigure = getBlocksToConfigure(); + + if(!blocksToConfigure.isEmpty()){ + BlocksToConfigureWidget *widget = new BlocksToConfigureWidget(blocksToConfigure, this, NULL); + widget->show(); + } +} + +void Parameters::connectionsValidation() { + +#ifdef DEBUG_INCLFUN + + QStack *interfaceToValidate = new QStack; + QList *validatedInterface = new QList; + + foreach(AbstractInterface *inter, topWindow->getScene()->getGroupItem()->getRefBlock()->getInterfaces()){ + foreach(AbstractInterface *connectedInter, inter->getConnectedTo()){ + + inter->setWidth(connectedInter->getWidth()); + interfaceToValidate->push(connectedInter); + } + } + + + try{ + while(!interfaceToValidate->isEmpty()){ + interfaceToValidate->pop()->connectionsValidation(interfaceToValidate, validatedInterface); + } + } + catch(Exception e){ + cerr << e.getMessage().toStdString() << endl; + } +#endif +} + +QList Parameters::getBlocksToConfigure() { + +#ifdef DEBUG_INCLFUN + + QList *checkedBlocks = new QList; + QList *blocksToConfigure = new QList; + + foreach(AbstractInterface *inter, topWindow->getScene()->getGroupItem()->getRefBlock()->getInterfaces()){ + foreach(AbstractInterface *connectedInter, inter->getConnectedTo()){ + if(!checkedBlocks->contains(connectedInter->getOwner())){ + connectedInter->getOwner()->parametersValidation(checkedBlocks, blocksToConfigure); + } + } + } + return *blocksToConfigure; +#endif +} + + +void Parameters::updateToolbar() { + int nb = currentScene->getBlockItems().length(); + for(int i = 0; igetBlockItems().at(i)->isSelected()){ + currentScene->getGroupWindow()->enableGroupButton(true); + return; + } + } + currentScene->getGroupWindow()->enableGroupButton(false); +} + + +void Parameters::updateIds() { + + /* a in-width cross of the graph must be done so that ids of GroupItem + are in the correct ordre when saving/loading a project + */ + int countItem = 1; + int countIface = 1; + QList fifo; + fifo.append(topScene); + while (!fifo.isEmpty()) { + GroupScene* scene = fifo.takeFirst(); + countItem = scene->setItemsId(countItem); + countIface = scene->setInterfacesId(countIface); + foreach(GroupScene* s, scene->getChildrenScene()) { + fifo.append(s); + } + } +} + + +ReferenceBlock *Parameters::searchBlockByXml(QString xmlName) { + foreach(ReferenceBlock *block, availableBlocks){ + if(block->getXmlFile().contains(xmlName)) + return block; + } + return NULL; +} + +ReferenceBlock *Parameters::searchBlockByMd5(QString sumMd5) { + foreach(ReferenceBlock *block, availableBlocks){ + if(block->getHashMd5() == sumMd5) + return block; + } + return NULL; +} + +void Parameters::save(QString confFile) { + +//#ifdef DEBUG_INCLFUN + + updateIds(); + QList allConnections; + QFile file(confFile); + if(file.open(QIODevice::WriteOnly)){ + + QXmlStreamWriter writer(&file); + + writer.setAutoFormatting(true); + writer.writeStartDocument(); + + writer.writeStartElement("blast_project"); + writer.writeStartElement("scenes"); + + writer.writeAttribute("count",QString::number(dispatcher->getNumberOfScenes())); + + // cross the scene level by level using a FIFO + QList fifoScene; + fifoScene.append(topScene); + foreach(ConnectionItem* item, topScene->getConnectionItems()) { + allConnections.append(item); + } + + GroupScene *scene; + while (!fifoScene.isEmpty()) { + scene = fifoScene.takeFirst(); + scene->save(writer); + foreach(GroupScene* s, scene->getChildrenScene()) { + fifoScene.append(s); + } + foreach(ConnectionItem* item, topScene->getConnectionItems()) { + allConnections.append(item); + } + } + + + writer.writeStartElement("connections"); + foreach(ConnectionItem* item, allConnections) { + + writer.writeStartElement("connection"); + + writer.writeAttribute("from",QString::number(item->getFromInterfaceItem()->getId())); + writer.writeAttribute("to", QString::number(item->getToInterfaceItem()->getId())); + + writer.writeEndElement(); + } + + writer.writeEndElement(); // + writer.writeEndElement(); //getId() == id) return scene; + GroupScene* sc = NULL; + + foreach(GroupScene *s, scene->getChildrenScene()) { + sc = searchSceneById(id,s); + if (sc != NULL) return sc; + } + return NULL; +} + +GroupItem* Parameters::searchGroupItemById(int id, GroupScene *scene) { + + if (scene->getGroupItem()->getId() == id) return scene->getGroupItem(); + + GroupItem* item = NULL; + foreach(GroupScene *s, scene->getChildrenScene()) { + item = searchGroupItemById(id,s); + if (item != NULL) return item; + } + return NULL; +} + +BoxItem* Parameters::searchBlockItemById(int id, GroupScene *scene) { + + foreach(BoxItem *item, scene->getBlockItems()){ + if(item->getId() == id){ + return item; + } + } + + BoxItem* item = NULL; + foreach(GroupScene *s, scene->getChildrenScene()) { + item = searchBlockItemById(id,s); + if (item != NULL) return item; + } + return NULL; +} + +InterfaceItem* Parameters::searchInterfaceItemById(int id, GroupScene* scene) { + + foreach(InterfaceItem *item, scene->getGroupItem()->getInterfaces()){ + if(item->getId() == id){ + return item; + } + } + foreach(BoxItem *block, scene->getBlockItems()){ + foreach(InterfaceItem *item, block->getInterfaces()){ + if(item->getId() == id){ + return item; + } + } + } + InterfaceItem* item = NULL; + foreach(GroupScene *s, scene->getChildrenScene()) { + item = searchInterfaceItemById(id,s); + if (item != NULL) return item; + } + return NULL; +} diff --git a/Parameters.h b/Parameters.h new file mode 100644 index 0000000..8393a79 --- /dev/null +++ b/Parameters.h @@ -0,0 +1,171 @@ +#ifndef __PARAMETERS_H__ +#define __PARAMETERS_H__ + +#include + +#include +#include +#include +#include +#include + +class Dispatcher; +class BlockLibraryTree; +class AbstractBlock; +class ReferenceBlock; +class GroupBlock; +class FunctionalBlock; +class GroupScene; +class GroupItem; +class BoxItem; +class InterfaceItem; +class Graph; + +#include "BlockImplementation.h" + +#include "Exception.h" +class Exception; + +// defines for current mode +#define MODE_EDITION 1 +#define MODE_ADDBLOC 2 +#define MODE_ADDCONN 3 + + +using namespace std; +using namespace Qt; + +class Parameters { + +public : + + enum Direction { NoDirection, East, North, West, South}; + + /* state of cursorn: + - CursorInBlock: cursor is within the main box of a block/group item + - CursorOnBlockBorder: cursor is on a block/group item border + - CursorOnInterface: cursor is on a block/group item Interface + - CursorOnConnection: cursor is one a connection + - CursorNowhere: none of the previous cases + */ + enum CursorState { CursorNowhere = 0, CursorInBlock, CursorInGroupTitle, CursorOnBorder, CursorOnInterface, CursorOnConnection }; + /* state of edition: + + */ + enum EditState { EditNoOperation = 0, EditBlockMove, EditBlockResize, EditGroupMove, EditGroupResize, EditInterfaceMove, EditInterfaceDeselect, EditConnectionResize, EditStartConnection, EditCloseConnection, EditAbortConnection}; + + enum XmlFileType { Configuration = 1, Reference, Implementation, Project }; + Parameters(); + ~Parameters(); + + // getter + inline GroupScene* getCurrentScene() { return currentScene; } + inline GroupScene* getTopScene() { return topScene; } + + // setter + inline void setTopScene(GroupScene* _topScene) { topScene = _topScene; } + inline void setCurrentScene(GroupScene* _currentScene) { currentScene = _currentScene; } + inline void setEditState(EditState state) { editState = state; } + inline void setCursorState(CursorState state) { cursorState = state; } + inline void setDispatcher(Dispatcher* _dispatcher) { dispatcher = _dispatcher;} + + /*************************************************** + attributes that are general to the application + ***************************************************/ + BlockLibraryTree* categoryTree; + QList refPathes; + QList implPathes; + QList availableBlocks; + QList availableImplementations; + + + QString refLib; + QString implLib; + + // defaults for vhdl + int wbDataWidth; + int wbAddressWidth; + // defaults for scene elements + int defaultBlockWidth; + int defaultBlockHeight; + QFont defaultBlockFont; + QString defaultBlockFontName; + int defaultBlockFontSize; + int arrowWidth; + int arrowHeight; + int arrowLineLength; + QFont defaultIfaceFont; + QString defaultIfaceFontName; + int defaultIfaceFontSize; + int connGapLength; + QPainterPath inArrow; + QPainterPath outArrow; + + /*************************************************** + attributes that are specific for the current project + ****************************************************/ + int sceneMode; // takes values from MODE_XXX + CursorState cursorState; + EditState editState; // takes values from EDIT_STATE_XXX + bool unsaveModif; + bool isRstClkShown; + + Graph* createGraph(); + void destroyGraph(); + inline Graph* getGraph() { return graph; } + GroupBlock* addGroupBlock(); // adding an empty GroupBlock to the current group + FunctionalBlock* addFunctionalBlock(int idCategory, int idBlock); // adding a functional block to current group + FunctionalBlock* duplicateFunctionalBlock(FunctionalBlock* block); // adding a copy of a functional block to current group + + + void clear(); + + QDomElement openProjectFile(const QString& projectFileName) throw(Exception); + void loadProject(QDomElement root); + + void loadBlastConfiguration(QString confFile) throw(Exception); + void loadReferencesFromXml() throw(Exception); + void loadReferencesFromLib() throw(Exception); + void saveReferencesToLib() throw(Exception); + + void loadImplementationsFromXml() throw(Exception); + void loadImplementationsFromLib() throw(Exception); + void saveImplementationsToLib() throw(Exception); + + void addAvailableBlock(ReferenceBlock *block); + void parametersValidation(); + void connectionsValidation(); + QList getBlocksToConfigure(); + + void updateToolbar(); + + + ReferenceBlock* searchBlockByXml(QString xmlName); + ReferenceBlock* searchBlockByMd5(QString sumMd5); + + void save(QString confFile); + + + QString projectPath; + +private: + Graph* graph; // the graph model of blocks + Dispatcher* dispatcher; + GroupScene* topScene; + GroupScene* currentScene; + + void setArrowPathes(); + void updateIds(); + + GroupScene* searchSceneById(int id, GroupScene* scene); + BoxItem* searchBlockItemById(int id, GroupScene* scene); + GroupItem* searchGroupItemById(int id, GroupScene* scene); + InterfaceItem* searchInterfaceItemById(int id, GroupScene *scene); + + void validateXmlFile(const QString& xmlFileName, const QString& xsdFileName, XmlFileType fileType) throw(Exception); + bool validate(QFile& fileXml, QFile& fileSchema); + +}; + + +#endif // __PARAMETERS_H__ diff --git a/ParametersWindow.cpp b/ParametersWindow.cpp new file mode 100644 index 0000000..07f96fa --- /dev/null +++ b/ParametersWindow.cpp @@ -0,0 +1,72 @@ +#include "ParametersWindow.h" +#include "Parameters.h" +#include "BlocksToConfigureWidget.h" +#include "BlockParameter.h" +#include "AbstractBlock.h" + +ParametersWindow::ParametersWindow(AbstractBlock *_block, Parameters *_params, BlocksToConfigureWidget *btcw, QWidget *parent) : + QWidget(parent) +{ + block = _block; + confWidget = btcw; + params = _params; + + layout = new QGridLayout; + + name = new QLabel; + value = new QLineEdit; + context = new QLabel; + type = new QLabel; + + comboBox = new QComboBox; + + foreach(BlockParameter *param, block->getParameters()){ + comboBox->addItem(param->getName()); + } + connect(comboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(updateData())); + updateData(); + + saveButton = new QPushButton("Save"); + connect(saveButton, SIGNAL(clicked()), this, SLOT(save())); + + layout->addWidget(new QLabel("Parameters"), 0, 0); + layout->addWidget(comboBox, 0, 1); + + layout->addWidget(new QLabel(" "), 1, 0); + + layout->addWidget(new QLabel("Name"), 2, 0); + layout->addWidget(name, 2, 1); + layout->addWidget(new QLabel("Value"), 3, 0); + layout->addWidget(value, 3, 1); + layout->addWidget(new QLabel("Context"), 4, 0); + layout->addWidget(context, 4, 1); + layout->addWidget(new QLabel("Type"), 5, 0); + layout->addWidget(type, 5, 1); + + layout->addWidget(saveButton, 6, 0); + + this->setLayout(layout); + + show(); +} + +void ParametersWindow::updateData() +{ + BlockParameter *param = block->getParameters().at(comboBox->currentIndex()); + name->setText(param->getName()); + value->setText(param->getValue().toString()); + context->setText(param->getContext()); + type->setText(param->getTypeString()); +} + +void ParametersWindow::save() +{ + BlockParameter *param = block->getParameters().at(comboBox->currentIndex()); + param->setValue(value->text()); + + //params->parametersValidation(); + if(confWidget != NULL){ + confWidget->updateBlocksList(); + } + close(); +} diff --git a/ParametersWindow.h b/ParametersWindow.h new file mode 100644 index 0000000..df6e4fc --- /dev/null +++ b/ParametersWindow.h @@ -0,0 +1,34 @@ +#ifndef __PARAMETERSWINDOW_H__ +#define __PARAMETERSWINDOW_H__ + +#include + +class Parameters; +class AbstractBlock; +class BlocksToConfigureWidget; + +class ParametersWindow : public QWidget { + Q_OBJECT + +public: + ParametersWindow(AbstractBlock *block, Parameters *_params, BlocksToConfigureWidget *btcw=NULL, QWidget *parent=0); + +private: + AbstractBlock *block; + Parameters *params; + BlocksToConfigureWidget *confWidget; + QGridLayout *layout; + QComboBox *comboBox; + QLabel *name; + QLabel *context; + QLineEdit *value; + QLabel *type; + QPushButton *saveButton; + +private slots: + void updateData(); + void save(); + +}; + +#endif // PARAMETERSWINDOW_H diff --git a/ReferenceBlock.cpp b/ReferenceBlock.cpp new file mode 100644 index 0000000..8b50bfa --- /dev/null +++ b/ReferenceBlock.cpp @@ -0,0 +1,471 @@ +#include "ReferenceBlock.h" + +#include "ReferenceInterface.h" +#include "BlockParameter.h" +#include "BlockParameterUser.h" +#include "BlockParameterGeneric.h" +#include "BlockParameterPort.h" +#include "BlockParameterWishbone.h" + +ReferenceBlock::ReferenceBlock(const QString _xmlFile) : AbstractBlock() { + xmlFile = _xmlFile; +} + +void ReferenceBlock::addCategory(int id) { + categories.append(id); +} + +void ReferenceBlock::setBriefDescription(const QString& str) { + if(str != NULL) + descriptionBrief = str; +} + +void ReferenceBlock::setDetailedDescription(const QString& str) { + if(str != NULL) + descriptionDetail = str; +} + +void ReferenceBlock::addImplementation(BlockImplementation *impl) { + implementations.append(impl); +} + +void ReferenceBlock::setHashMd5() { + QFile file(xmlFile); + if (file.open(QIODevice::ReadOnly)) { + QByteArray fileData = file.readAll(); + QByteArray hashData = QCryptographicHash::hash(fileData, QCryptographicHash::Md5); + hashMd5 = QString(hashData.toHex()); + cout << qPrintable(xmlFile) << " has md5 hash : " << qPrintable(hashMd5) << endl; + } + else { + hashMd5 = ""; + } +} + +void ReferenceBlock::load(QDomElement &elt) throw(Exception) { + + + cout << "Block : get informations" << endl; + QDomElement eltInfo = elt.firstChildElement("informations"); + try { + loadInformations(eltInfo); + } + catch(int err) { + throw(err); + } + + cout << "Block : get params" << endl; + QDomElement eltParams = eltInfo.nextSiblingElement("parameters"); + try { + loadParameters(eltParams); + } + catch(int err) { + throw(err); + } + + cout << "Block : get interfaces" << endl; + QDomElement eltInter = eltParams.nextSiblingElement("interfaces"); + try { + loadInterfaces(eltInter); + } + catch(int err) { + throw(err); + } +} + +void ReferenceBlock::loadInformations(QDomElement &elt) throw(Exception) { + + bool ok; + if ((elt.isNull()) || (elt.tagName() != "informations")) throw (Exception(BLOCKFILE_CORRUPTED)); + // getting name + cout << "Block info : get name" << endl; + QDomNode nodeName = elt.firstChild(); + QDomNode nodeNameTxt = nodeName.firstChild(); + if (nodeNameTxt.isNull()) { + name = "no_name"; + } + else { + QDomText txtName = nodeNameTxt.toText(); + name = txtName.data().trimmed(); + cout<< "block name : " << qPrintable(name) << endl; + } + + // getting categories + cout << "Block info : get categories" << endl; + QDomElement eltCat = nodeName.nextSiblingElement("category"); + + QString idsStr = eltCat.attribute("ids","none"); + if (idsStr == "none") throw (Exception(BLOCKFILE_CORRUPTED)); + QStringList listCat = idsStr.split(","); + foreach(QString str, listCat) + { + int idCat = str.toInt(&ok); + categories.append(idCat); + } + + // getting description + cout << "Block info : get description" << endl; + QDomElement eltDesc = eltCat.nextSiblingElement("description"); + // getting brief + QDomElement eltBrief = eltDesc.firstChildElement("brief"); + QDomNode nodeBriefTxt = eltBrief.firstChild(); + if (nodeBriefTxt.isNull()) { + descriptionBrief = "no brief description"; + } + else { + QDomText txtBrief = nodeBriefTxt.toText(); + descriptionBrief = txtBrief.data().trimmed(); + cout << "block brief desc : " << qPrintable(descriptionBrief) << endl; + } + // getting detailed + QDomElement eltDetail = eltBrief.nextSiblingElement("detailed"); + QDomNode nodeDetailTxt = eltDetail.firstChild(); + if (nodeDetailTxt.isNull()) { + descriptionDetail = "no detailed description"; + } + else { + QDomText txtDetail = nodeDetailTxt.toText(); + descriptionDetail = txtDetail.data().trimmed(); + cout << "block detail desc : " << qPrintable(descriptionDetail) << endl; + } +} + +void ReferenceBlock::loadParameters(QDomElement &elt) throw(Exception) { + + if ((elt.isNull()) || (elt.tagName() != "parameters")) throw (Exception(BLOCKFILE_CORRUPTED)); + + QDomNodeList listNodeParam = elt.elementsByTagName("parameter"); + for(int i=0; igetName().toStdString() << endl; + cout << "purpose for " << nameStr.toStdString() << " : " << purposeStr.toStdString() << endl; + purpose = ReferenceInterface::translatePurpose(purposeStr); + cout << "translated purpose : " << purpose << endl; + levelStr = eltInput.attribute("level","none"); + level = ReferenceInterface::translateLevel(levelStr); + multStr = eltInput.attribute("multiplicity","none"); + mult = ReferenceInterface::translateMultiplicity(multStr); + + inter = new ReferenceInterface(this,nameStr,typeStr,widthStr,AbstractInterface::Input, purpose, level, mult); + inputs.append(inter); + } + + QDomElement eltOutputs = eltInputs.nextSiblingElement("outputs"); + QDomNodeList listNodeOutputs = eltOutputs.elementsByTagName("output"); + for(int i=0;i *checkedBlocks, QList *blocksToConfigure) +{ + return; +} + +/* operator<<() : + only used to save all ReferenceBlock in a library in binary format, so that reference blocks + are read very fast at application startup. + + */ +QDataStream& operator<<(QDataStream &out, const ReferenceBlock &b) { + + out.setVersion(QDataStream::Qt_5_0); + + QByteArray blockData; + QDataStream toWrite(&blockData, QIODevice::WriteOnly); + + toWrite << b.name; + toWrite << b.xmlFile; + toWrite << b.descriptionBrief; + toWrite << b.descriptionDetail; + toWrite << b.categories; + toWrite << b.hashMd5; + toWrite << b.params.size(); + BlockParameter* p; + for(int i=0;igetContext(); + toWrite << p->getName(); + toWrite << p->getTypeString(); + toWrite << p->getValue().toString(); + if (p->isPortParameter()) { + toWrite << ((BlockParameterPort*)p)->getIfaceName(); + } + else if (p->isWishboneParameter()) { + BlockParameterWishbone* pwb = (BlockParameterWishbone*)p; + toWrite << pwb->getWidth(); + toWrite << pwb->getWBAccess(); + toWrite << pwb->getWBValue(); + toWrite << pwb->getWBDuration(); + } + + } + + toWrite << b.inputs.size(); + for(int i=0; igetName(); + toWrite << iface->getWidth(); + toWrite << iface->getPurpose(); + toWrite << iface->getDirection(); + toWrite << iface->getLevel(); + toWrite << iface->getMultiplicity(); + } + toWrite << b.outputs.size(); + for(int i=0; igetName(); + toWrite << iface->getWidth(); + toWrite << iface->getPurpose(); + toWrite << iface->getDirection(); + toWrite << iface->getLevel(); + toWrite << iface->getMultiplicity(); + } + toWrite << b.bidirs.size(); + for(int i=0; igetName(); + toWrite << iface->getWidth(); + toWrite << iface->getPurpose(); + toWrite << iface->getDirection(); + toWrite << iface->getLevel(); + toWrite << iface->getMultiplicity(); + } + + out << blockData; + + return out; +} + +QDataStream& operator>>(QDataStream &in, ReferenceBlock &b) { + + quint32 blockSize; + ReferenceInterface* iface; + BlockParameter* p; + int val; + QString txt=""; + + in.setVersion(QDataStream::Qt_5_0); + + in >> blockSize; + + in >> b.name; + in >> b.xmlFile; + in >> b.descriptionBrief; + in >> b.descriptionDetail; + in >> b.categories; + in >> b.hashMd5; + b.params.clear(); + int nb; + in >> nb; + cout << qPrintable(b.name) << " has " << nb << " parameters" << endl; + for(int i=0;i> contextStr; + in >> nameStr; + in >> typeStr; + in >> valueStr; + + if (contextStr == "user") { + p = new BlockParameterUser(&b,nameStr,valueStr); + } + else if (contextStr == "generic") { + p = new BlockParameterGeneric(&b,nameStr,typeStr,valueStr); + } + else if (contextStr == "port") { + QString ifaceStr = ""; + in >> ifaceStr; + p = new BlockParameterPort(&b,nameStr,valueStr,ifaceStr); + } + else if (contextStr == "wb") { + QString widthStr = ""; + int wbAccess; + QString wbValue; + int wbDuration; + in >> widthStr; + in >> wbAccess; + in >> wbValue; + in >> wbDuration; + p = new BlockParameterWishbone(&b,nameStr,typeStr,widthStr,valueStr,wbAccess,wbValue,wbDuration); + } + b.params.append(p); + } + + b.inputs.clear(); + in >> nb; + for(int i=0;i> txt; + iface->setName(txt); + in >> txt; + iface->setWidth(txt); + in >> val; + iface->setPurpose(val); + in >> val; + iface->setDirection(val); + in >> val; + iface->setLevel(val); + in >> val; + iface->setMultiplicity(val); + b.inputs.append(iface); + } + + b.outputs.clear(); + in >> nb; + for(int i=0;i> txt; + iface->setName(txt); + in >> txt; + iface->setWidth(txt); + in >> val; + iface->setPurpose(val); + in >> val; + iface->setDirection(val); + in >> val; + iface->setLevel(val); + in >> val; + iface->setMultiplicity(val); + b.outputs.append(iface); + } + + b.bidirs.clear(); + in >> nb; + for(int i=0;i> txt; + iface->setName(txt); + in >> txt; + iface->setWidth(txt); + in >> val; + iface->setPurpose(val); + in >> val; + iface->setDirection(val); + in >> val; + iface->setLevel(val); + in >> val; + iface->setMultiplicity(val); + b.bidirs.append(iface); + } + + return in; +} diff --git a/ReferenceBlock.h b/ReferenceBlock.h new file mode 100644 index 0000000..324e289 --- /dev/null +++ b/ReferenceBlock.h @@ -0,0 +1,69 @@ +#ifndef __REFERENCEBLOCK_H__ +#define __REFERENCEBLOCK_H__ + +#include + +#include +#include +#include +#include + +#include "AbstractBlock.h" +class AbstractBlock; + +#include "BlockImplementation.h" + +#include "Exception.h" +class Exception; + +using namespace std; +using namespace Qt; + + +class ReferenceBlock : public AbstractBlock { + +public: + + ReferenceBlock(const QString _xmlFile); + + int getType(); + inline QString getXmlFile() { return xmlFile; } + inline QString getBriefDescription() { return descriptionBrief; } + inline QString getDetailedDescription() { return descriptionDetail; } + inline QList getCategories() { return categories; } + inline QList getImplementations() { return implementations; } + inline QString getHashMd5() { return hashMd5; } + + inline AbstractBlock* getParent() { return NULL; } + + void addCategory(int id); + void setBriefDescription(const QString& str); + void setDetailedDescription(const QString& str); + void addImplementation(BlockImplementation* impl); + + void load(QDomElement &elt) throw(Exception); + void setHashMd5(); + +private: + QString xmlFile; // the xml file from which attributes are initialized. + QString hashMd5; + QString descriptionBrief; + QString descriptionDetail; + QList categories; + QList implementations; // set when implementations are read + + // loading methods for the different tags in the XML desc. + void loadInformations(QDomElement &elt) throw(Exception); + void loadParameters(QDomElement &elt) throw(Exception); + void loadInterfaces(QDomElement &elt) throw(Exception); + + friend QDataStream &operator<<(QDataStream &out, const ReferenceBlock &b); + friend QDataStream &operator>>(QDataStream &in, ReferenceBlock &b); + + + // AbstractBlock interface +public: + void parametersValidation(QList* checkedBlocks, QList* blocksToConfigure); +}; + +#endif // __REFERENCEBLOCK_H__ diff --git a/ReferenceInterface.cpp b/ReferenceInterface.cpp new file mode 100644 index 0000000..8aca132 --- /dev/null +++ b/ReferenceInterface.cpp @@ -0,0 +1,73 @@ +#include "ReferenceInterface.h" +#include "AbstractBlock.h" + +ReferenceInterface::ReferenceInterface(AbstractBlock* _owner) throw(Exception) : AbstractInterface(_owner) { + if (_owner->isReferenceBlock()) throw(Exception(BLOCK_INVALID_TYPE)); + multiplicity = 1; +} + +ReferenceInterface::ReferenceInterface(AbstractBlock* _owner, + const QString& _name, const QString&_type, + const QString& _width, + int _direction, + int _purpose, + int _level, + int _multiplicity) +throw (Exception) : AbstractInterface(_owner, _name, _type, _width, _direction, _purpose, _level) { + + if (_owner->isReferenceBlock()) throw(Exception(BLOCK_INVALID_TYPE)); + + multiplicity = _multiplicity; + if (direction == InOut) { + multiplicity = 1; + } +} + +bool ReferenceInterface::isReferenceInterface() { + return true; +} + +void ReferenceInterface::setMultiplicity(int _multiplicity) { + if (direction == InOut) { + multiplicity = 1; + } + else { + multiplicity = _multiplicity; + } +} + +int ReferenceInterface::translatePurpose(const QString& txt) { + if (txt == "clock") { + return Clock; + } + else if (txt == "reset") { + return Reset; + } + if (txt == "wb") { + return Wishbone; + } + return Data; +} + +int ReferenceInterface::translateLevel(const QString& txt) { + + if (txt == "top") { + return Top; + } + return Basic; +} + +int ReferenceInterface::translateMultiplicity(const QString& txt) { + bool ok; + int mult; + if (txt == "*") { + mult = -1; + } + else { + mult = txt.toInt(&ok); + if (!ok) { + mult = 1; + } + } + return mult; +} diff --git a/ReferenceInterface.h b/ReferenceInterface.h new file mode 100644 index 0000000..cd5bd0f --- /dev/null +++ b/ReferenceInterface.h @@ -0,0 +1,48 @@ +#ifndef __REFERENCEINTERFACE_H__ +#define __REFERENCEINTERFACE_H__ + +#include + +#include +#include + +#include "AbstractInterface.h" + +#include "Exception.h" + +using namespace std; +using namespace Qt; + + +class ReferenceInterface : public AbstractInterface { + +public : + + ReferenceInterface(AbstractBlock *_owner) throw(Exception); + ReferenceInterface(AbstractBlock* _owner, const QString& _name, const QString& _type, const QString& _width, int _direction, int _purpose, int _level, int _multiplicity=1) throw (Exception); + + // getters + inline int getMultiplicity() { return multiplicity; } + inline AbstractInterface* getConnectedFrom() { return NULL; } + // setters + void setMultiplicity(int _multiplicity); + + // testers + bool isReferenceInterface(); + + // others + + static int translatePurpose(const QString& txt); + static int translateLevel(const QString& txt); + static int translateMultiplicity(const QString& txt); + + inline AbstractInterface *clone(){ return NULL; } + + +private: + int multiplicity; // -1 means infinite multiplicity, and X>1, the max. number of instances + + +}; + +#endif // __REFERENCEINTERFACE_H__ diff --git a/Toto.cpp b/Toto.cpp new file mode 100644 index 0000000..0e5fb15 --- /dev/null +++ b/Toto.cpp @@ -0,0 +1,12 @@ +#include "Toto.h" + +#include "FunctionalBlock.h" +#include "ReferenceBlock.h" +#include "ReferenceInterface.h" +#include "FunctionalInterface.h" +#include "BlockParameter.h" + + +Toto::Toto(const QString& msg) { + v = msg; +} diff --git a/Toto.h b/Toto.h new file mode 100644 index 0000000..1980e26 --- /dev/null +++ b/Toto.h @@ -0,0 +1,35 @@ +#ifndef __TOTO_H__ +#define __TOTO_H__ + +#include +#include + +#include +#include + +class ReferenceBlock; +class FunctionalBlock; +class AbstractInterface; + + +#include "ArithmeticEvaluator.h" +class ArithmeticEvaluator; + +#include "Exception.h" +class Exception; + +using namespace std; +using namespace Qt; + +class Toto { + +public: + + Toto(const QString& msg); + +private: + QString v; +}; + +#endif // __TOTO_H__ + diff --git a/blast-tg.odt b/blast-tg.odt new file mode 100755 index 0000000000000000000000000000000000000000..c85bfebc84f88d589387402a8a5f919a7ca430b2 GIT binary patch literal 31472 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 literal 0 HcmV?d00001 diff --git a/blast.config b/blast.config new file mode 100755 index 0000000..e0284f4 --- /dev/null +++ b/blast.config @@ -0,0 +1,2 @@ +// Add predefined macros for your project here. For example: +// #define THE_ANSWER 42 diff --git a/blast.cpp b/blast.cpp new file mode 100644 index 0000000..b205eb2 --- /dev/null +++ b/blast.cpp @@ -0,0 +1,18 @@ +#include +#include +#include "MainWindow.h" +#include + +using namespace std; +using namespace Qt; + +int main(int argc, char *argv[]) +{ + QApplication a(argc, argv); + MainWindow w; + + w.show(); + + return a.exec(); + +} diff --git a/blast.creator b/blast.creator new file mode 100755 index 0000000..e94cbbd --- /dev/null +++ b/blast.creator @@ -0,0 +1 @@ +[General] diff --git a/blast.creator.user b/blast.creator.user new file mode 100755 index 0000000..9b8a007 --- /dev/null +++ b/blast.creator.user @@ -0,0 +1,193 @@ + + + + + + EnvironmentId + {3701e197-5b6c-48ea-9e98-a6cf6de18672} + + + ProjectExplorer.Project.ActiveTarget + 0 + + + ProjectExplorer.Project.EditorSettings + + true + false + true + + Cpp + + CppGlobal + + + + QmlJS + + QmlJSGlobal + + + 2 + UTF-8 + false + 4 + false + 80 + true + true + 1 + true + false + 0 + true + 0 + 8 + true + 1 + true + true + true + false + + + + ProjectExplorer.Project.PluginSettings + + + + ProjectExplorer.Project.Target.0 + + Desktop + Desktop + {ed04208c-8774-456b-99b9-4a02094ca7a4} + 0 + 0 + 0 + + /home/sdomas/Projet/Blast/code/v0.2 + + + + all + + false + + + true + Make + + GenericProjectManager.GenericMakeStep + + 1 + Compiler + + ProjectExplorer.BuildSteps.Build + + + + + clean + + true + + + true + Make + + GenericProjectManager.GenericMakeStep + + 1 + Nettoyer + + ProjectExplorer.BuildSteps.Clean + + 2 + false + + Défaut + Défaut + GenericProjectManager.GenericBuildConfiguration + + 1 + + + 0 + Déploiement + + ProjectExplorer.BuildSteps.Deploy + + 1 + Déployer localement + + ProjectExplorer.DefaultDeployConfiguration + + 1 + + + + false + false + false + false + true + 0.01 + 10 + true + 1 + 25 + + 1 + true + false + true + valgrind + + 0 + 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10 + 11 + 12 + 13 + 14 + + 2 + + + + false + %{buildDir} + Exécutable personnalisé + + ProjectExplorer.CustomExecutableRunConfiguration + 3768 + false + true + false + false + true + + 1 + + + + ProjectExplorer.Project.TargetCount + 1 + + + ProjectExplorer.Project.Updater.FileVersion + 16 + + + Version + 16 + + diff --git a/blast.files b/blast.files new file mode 100755 index 0000000..7b2dd43 --- /dev/null +++ b/blast.files @@ -0,0 +1,73 @@ +Exception.h +Exception.cpp +AbstractBlock.h +AbstractBlock.cpp +AbstractBoxItem.h +AbstractBoxItem.cpp +FunctionalBlock.cpp +FunctionalBlock.h +GroupBlock.h +GroupBlock.cpp +BlockImplementation.h +BlockImplementation.cpp +GroupScene.cpp +GroupScene.h +ReferenceBlock.cpp +ReferenceBlock.h +AbstractInterface.h +AbstractInterface.cpp +ConnectedInterface.h +ConnectedInterface.cpp +ReferenceInterface.h +ReferenceInterface.cpp +FunctionalInterface.cpp +FunctionalInterface.h +GroupInterface.h +GroupInterface.cpp +BlockLibraryWidget.cpp +BlockLibraryWidget.h +blast.cpp +BlockCategory.cpp +BlockCategory.h +BoxItem.cpp +BoxItem.h +BlockLibraryTree.cpp +BlockLibraryTree.h +BlockParameter.cpp +BlockParameter.h +BlockParameterUser.h +BlockParameterUser.cpp +BlockParameterGeneric.h +BlockParameterGeneric.cpp +BlockParameterWishbone.h +BlockParameterWishbone.cpp +BlockParameterPort.h +BlockParameterPort.cpp +Dispatcher.cpp +Dispatcher.h +Graph.cpp +Graph.h +MainWindow.cpp +MainWindow.h +Parameters.cpp +Parameters.h +ConnectionItem.h +ConnectionItem.cpp +InterfaceItem.h +InterfaceItem.cpp +GroupItem.h +GroupItem.cpp +Abstractblockitem.h +Abstractblockitem.cpp +GroupWidget.h +GroupWidget.cpp +BlockWidget.cpp +BlockWidget.h +BlocksToConfigureWidget.h +BlocksToConfigureWidget.cpp +ParametersWindow.h +ParametersWindow.cpp +ArithmeticEvaluator.cpp +ArithmeticEvaluator.h +InterfacePropertiesWindow.h +InterfacePropertiesWindow.cpp diff --git a/blast.ico b/blast.ico new file mode 100755 index 0000000000000000000000000000000000000000..6f1794d58295508d2b868a1d7dfdc72245bc1352 GIT binary patch literal 9662 zcmeI2OO6vk42B&=f+ah)umEY6a{vUF-~e*~j({LX;0DGwEuYdQ)f^k{bGs~XyA)Y_b zl`RYMXWJa#m;C#iuS4HqJcn9+7AhV8E9RGcY@R~}|u@1&p4`J9b`(UnhWv2>G<~!wPt85W88!tNPm5=1M}zjMLX7tjOjDu z;MeQ3_;c-nY059V!v*VS_`r&szu^AH@ke`@3)_;>bM`Qw%dd5sSo756T)+saxiL2E z5v}n8Mx&oMq<()%>kH{JV_*z%>oLa>x7MG5(LL;`%C8yW3#8cB^C^6nL-YA17h+|S zC%h=w*L>jCW3tcm=i5VWP4nOKK(8H7_BC6q9q~Qi9{5G3eMquKIL7CU=9jfNkJ!E$ z(h!|B&>I#if^0{%yoj70 + + icons/copy.png + icons/cut.png + icons/delete.png + icons/inter_add.png + icons/link.png + icons/paste.png + icons/save.png + icons/redo-icon.png + icons/undo.png + icons/save-as.png + icons/open.png + icons/load.png + icons/window_new.png + icons/new.ico + icons/folder_add_16.png + icons/add_block.png + icons/edit_block.png + icons/add_group_void.png + icons/add_group_select.png + icons/add_connection.png + + diff --git a/blast.rc b/blast.rc new file mode 100755 index 0000000..c6b4512 --- /dev/null +++ b/blast.rc @@ -0,0 +1 @@ +IDI_ICON1 ICON DISCARDABLE "blast.ico" \ No newline at end of file diff --git a/blastconfig.xml b/blastconfig.xml new file mode 100644 index 0000000..a8184b8 --- /dev/null +++ b/blastconfig.xml @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/blastconfig.xsd b/blastconfig.xsd new file mode 100644 index 0000000..f254ff6 --- /dev/null +++ b/blastconfig.xsd @@ -0,0 +1,210 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/block-1I1O.xml b/block-1I1O.xml new file mode 100644 index 0000000..32efdb5 --- /dev/null +++ b/block-1I1O.xml @@ -0,0 +1,34 @@ + + + + + block-1I1O + + + + + A testing block with 1 input, 1 output + + + A testing block with 1 input, 1 output + + + + + + + + + + + + + + + + + + + + + diff --git a/block-2I2O.xml b/block-2I2O.xml new file mode 100644 index 0000000..54c5460 --- /dev/null +++ b/block-2I2O.xml @@ -0,0 +1,36 @@ + + + + + block-2I2O + + + + + A testing block with 2 inputs, 2 outputs + + + A testing block with 2 inputs, 2 outputs + + + + + + + + + + + + + + + + + + + + + + + diff --git a/block-2INO.xml b/block-2INO.xml new file mode 100644 index 0000000..d654234 --- /dev/null +++ b/block-2INO.xml @@ -0,0 +1,35 @@ + + + + + block-2INO + + + + + A testing block with 2 inputs, N outputs + + + A testing block with 2 inputs, N outputs + + + + + + + + + + + + + + + + + + + + + + diff --git a/block.xsd b/block.xsd new file mode 100644 index 0000000..42c82ef --- /dev/null +++ b/block.xsd @@ -0,0 +1,190 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/doc/models-tg.pdf b/doc/models-tg.pdf new file mode 100644 index 0000000000000000000000000000000000000000..86606a75b1d92096d441b8065d151308f815ea0c GIT binary patch literal 94799 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Ⓢ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

TUj4pq$<9L>c%7&>J}RtEM|4lt?pufZPb~GQ;2L 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 z z)tx}(&1dazTd`DoYg?Hou?(WDfDU0=!1BK#yO2`t2m#2~(DzT@H+}r>paO(_;5IR! z8iAL63cq_B3nvLT_`Meo_Y4pMA_yZr1Oj$zcY4*$DDBqz@(^cwxBL;*1++z##pmKr z+y{R`4-XCS0rou*1pq!GBtd|Jf(#;IsMhN@b5tzZYb`bCPpoRl`T$_xKT>pSz8Bi< z!}>kTA1Vmuc1A~w0o7O#Fx?AfM@>m=5%UiC*pFe0-~Oa6ZXtt;4ez`ji{(G-Kg$e|hd>SF>3@D}O82;w`K|e=kxAc7xAKv)PPcrM zhD6PHgKzJ92}WlQss6b$Lh>WdTzrXmyr>{Psh|9{(EQRGs z{+pg;acl5x$Hck=^A773)`ZQcM%O1A7>^NWc4Kk1L05+W`QlJ1tKFU2d(kCj5-wP# z`oc~nvSO#58tWj6Et(1LSBAkv{diW9VZY!#DXKHy8LPe-VeqTOgp{?+V7R9YAH(=HZR?ZP)pe7*V?VJEp=V-;sl(bC8e+rn zS#wRXJNVAG<@7Y$uYO5sk^sqaY^>(hvcz4Lp{xLsEYDk2+d1KnCERwqYrh#>Q3>*}Ar+QvV3{#yHY^KXxUA@I*AJir^`4OI zmHs;GOt)Gsa$FZez@HuobOb2+m{p9^SFLE-3@?)dLqqsM%OcDV&m6yD1H$duIwQ8- zMsfEWy%iLhl<1e^`I{&_Bv}t^%{6HT>dKI2na`&8!pMrZ9Et>uZQV;6@9qB5>V0M_ z`FE^HgxZd*jL>3M|@G-vGBTtKt`F_neWW7F;8~aog+bEq6SB*%Xn@~{2P=+oPH=z zG8&_e1+3vNj~+k_wKF`M_K9ub&>G!1%YeB(O%8X1mU#Gp1y0ix$0cdZgITfV`;iD@ z|B}U4#ae4voIVilk}hr^x~>1V&N-pHM;A0+ml@XT3}|l#XH~nVn(g+yP`>!YfsZymIH^#?Q*nJScpPkQH?ef`w(xa%IAO2qTO<+_?vG)SP^9(!J z!ljjkZLAfW%Z;k>!nxegA+>mLnxZfbDjtBw4u>O|N$wmfvBR@&t5=?R$nHP#O=n6M zOa$K|Kp*RoCGWDmGq;Gn(zN)Q_QK3Mr{h zmn9WfD5V_aR*~7rpIJGGO#*I^7sP*P!OXrl=lTX5u5a@Ib?0j$mNT+cX>b z$Co$aUpE`G@a^l2jhO(6JxZ83gMXRyDDNs44l(i_dPii{=-NiPB^vs@lIy>mQ&8VE zHXrCqmQo_e?%0gQ_sg^Ut&~aROy0tW!ZzAUtFI*|d`(NIymopKv%^D}#De3vPcJTf zR*)%SD|&OkuDp+ItH`LM!Al*KvI2Z)+(|8NQN!kn&_I4`$6b{{6_3Jin73k9%YkbC z7O&8OZJL{6agYJgf}%8qn4b}wa2VlYg+Pg8HBS6(SS(}D!-B0%GWhpX=i~Yh7^`M< z?6e(nzH(&T?MupNw0~Qg7{M9|fLr)xTFJDSeeCN!TAhsacUNAswCi)d!a6L`O6z6i zNGqEA2DDY@=f9%10+9{quWaO?a>v{pN-WVXt_1bPX9bvcLI(vWY9GrP(T}EDFy^p- zi=8a@hPny}#)p^B);Fr^oA3pr1{XZnu4DUt@sCwY??wpNLuHpECobVKc!!W_qoY|J(Sq3II#HM1Xtn0$-jkyii+8dt02F2Y;`jT0>pXFwcgKzX;lFny8UaDD4+r7Yc8`ekNVD}Qe8qp>PSA1

DD#bUHhFN+jaLu=@_EgFt!>T>Y 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 Pm&mqAav=KMm67p(NPfAJ literal 0 HcmV?d00001 diff --git a/icons/new_block.ico b/icons/new_block.ico new file mode 100644 index 0000000000000000000000000000000000000000..7530036d0ae0a2fe4326f9c6d665851d76e872e6 GIT binary patch literal 5939 zcmb7IXH-*Nm%fPsLJI)_>0MAdQly6_MWqT#6G22lAQGxngA$0+1qEM3nuKmY-N_Y45AgX^be zCI*ajTy)@)(a2EG;zWA#gHwYKd++?apxT^~p0?$~^py-x51#hGc1m!7NNBF1mHMlf zo?qWdR-+jk%dQ`>2%8CInLLKMdRDpaUy?e^h9`s>CdtHdSLHKvlPwe!`?$`T5lrTr z(R~8`ws6C~R^Le=ZqC|YMC_AGb@DcsjX@&o*VBOkYDa|eLGLP=(qI|+7BU4p-_z5z zs%hV|`TE{nQXC){fz*Ku4sZ17GHHAmr?%Vh<<8P6w+-bW@IzB!>uNxl?tATsR(blL zp1>gBP+%!8v#cn_nM0LjTNK7c@Uiv_(eb#$MR0y`+2wL6Zz8KNH`1Wo_DBhPLz^V# zFHie(=z6dcLRc6(+_HaV(jPhCa-3~Z5Mouy>=bhvdwrBrh*pCyG9O&!LL`H>O+H3A zoKALqU?yh&63-<#WO{ep4%!PD{UKIrA$K2<8#vx!%`v{^wrt*01cKH9)l1D@4{th1R@n&flX9Nd1r=k$>A;?Hzy|aK4Ie*QW;r zb1TgNIEI4`5Jc!g1X`WW0TId!R9IwGHh`RwWd(G2BB5INmj6vj|LC4T66&IHhkgX} z|KAOY9&|4Y{Xc|m!D)fmgaMuX-emMC=>LFpT~R;~(E|UUchE9|{oBny?QD-7G<1Uf z^Ny#XU`k^CJzZiDS`3H&zb7u~p98fRX@Ob&_f!!@{u#L!O9C=V?lJ&hq*BBnt|+IG z>*Usy7#y!LNnW6#1AcgYFTuwDMR~e8<7atPsai7(C^^|V$)LrpzBjnLW6x8zn|=qPu?1O=#O&hn%{09Hwr)Zx9n$={G-C1F`|~ zZcxx)bXwlb%9PfQQ&ldVIp^5Ex$Y@;N%ObQd|R9t;gy-j^*5~m*iMPOjixSoYnxXq zmauoCVq&}VN`=v80Dk*$>~(YYui4R?KjJ-wcXNm%>%_Nf#O4|9o7Kl=1&?{tP}2=N z{q((r;)@#v1C75jL=^OaDg@Trn3rC8AcX6*Fq7kW$5EB_0o9cH!Nr?CVSqK-azlF? zh;w*I4S11SZd`jI3tU_dt;ERV3b-ons`vl!~7@oatTH-qz|}RScL{KG6>_ z386}|JBCwk90g3wgfgFvzQuL{DsbKB3knZHiO20PW~x3S%zSg>sb6QPh@FVnaf9D$ zbJu9RCj++8`+{e}qqYt7?eAr_Rx+a{@-W`30%(pSgmIWmrP~UD?@mY{*`tJ2!2S?_ftpSHB}qOJ9PS&fQUOSEi>OZ7i@d|-^x8P z<{0u`LpQ;(*3sBLRm3fHm)2J3PIfH^8)Q`dr4W~FZuRH{I+g{{`Fy*0`4uDztnL_d zW^8vh>mo`Az9NXlPOiNCuNscwf+C-@1wVNpFMApA;JcD|TsmgE6;{AT0^t2P+Ay)0 z{p5fS59MBMTOOC$E84$_`AC~ z6k!4)r*HeQJ5(~(7%#y7cC(0rQiq)70Ko}X=$x>gGE89jc55Dlis6$GN}m=GeARyx ztIlo;Kv{v?ikXnm;z>G!3UFIWl!4RuQ}*c-$p|9s?~#l zNb_NSHUF5$Xk-coN^0d!ZI5zwG_O=y&Ua?soY5@;`xoXsCH1PuElWvFO!Hx_fhg`3 z^*R?eg|hT9snne99B_yC%myMZ`iwc7fnSOQ>4IhxsXt!XekMkLhhVT#m9@00(0u zyL2Y?ddJ9`>W?^0Wg4w9o(&4$z1H~g_MDLk{J1EeQqST@_WNu zK~L`6Tu=@~6he17&Fk5B}*-WQU2c z$riBr^Fc(fs#~hcwjlSnIb*N0uziUNm>ug^`{cY?56V;RG@rEaYeegp@-lWor`9yX zcDEF$uvw9tdnFXo%IwpAHLu>-c&8H0Cjq_n5wG5!<|+~nJkQoSTD~nmTJ0k9>t*jZ zA=&M{$-#qj0L{sFD#l=JJaQc*ZB5z%U#yEe=!@wKLZw}dwmX^3F6jAs1|4K`ja`YIB2lb8m>uFY;Yf>Dq%~uluXU zG4a2fyJqgs-Ae|L8ZeemmW>f-8-9)VrDtlySd>g0r(FXK?I3sAO*pEnaK7>6F&oh0 zJ|zPzMg(NPcS-mb@9E(qF6Es7+)!hwIaF&4*@5;mUlUt4d<3UtoCo5B$542XZS4&x zs1ZRfaKM0tVE71p8d8|G9y-A>4ANBoywo>MoGoD|Fo~xFxWSpQ5UvXZ5Rn8w4ybah zIWrIi9NqI{F{@R$7rL<3HEX-&sDjL@~+3`no<> z#q1ug&FXHnY<+7LeFstB*%G@Rd<^8ke)eVSvI4r+1gdoQ;@R{)l_;v63-pe31PRke zGe@Kx@e^7OkqmqKdbsiJP-IctbhB9WbVtzG_t4$R%HlIScW^#Ht8E12O6rzfA18?O z_a%;>SYs{LGP}f+JS9lNfxbiYyF^aKXFW0CNyD*snj_!<$j<+g;&+(3m8`+LmUZk3 zA|JFM{4#synst7w7eZL|cL$ICIqv;?J0pj4Kr33ArjJ>l_T*~l2`zyO!XhBiHlFCp z6RP^LUiK)vfEN0l)}HRF=FZJTk{k5Aj7*GZv=>V#Ax1QEmuN?yfP=0VV9$ zgn%^goIMb4@dySpV07|trYE|+#*KthOu(5{+doMjTcLpLCnp*MlyUcQf=j9Q#Xo%? z3F3#x0~pRgnNRLQ_@RYN?0?#H_VYejZg_MLOx)1IYPjEY<24ZaeaT(a3ixW$^>rku z*pmey-Q|c9O}>AN9l-eD{C&ebJ~FcmUNt4Z4Dvx1UO!nl0m`*9@Mp=%aRHF^(HOgx0q>$g%U2>EXLvDHTp$WDhleR-CJJ^Kxm zzJhCcQ%$z*@!n_^8FN6D8GaUm_u8tO_3Poe#y4f~(sQ~Y^ihd*$n`eO8R@7Lhc7ZI z+ua2kHDp>3@t8cTG&>s`a&xs0skw)L%3vP1i&@mmHe-&oh9snGc4Ov!zn9#%39j84 zb6=B8=`;<>+|?SB(V4i@^tx&HSLT5C+VbVvjp}|GvEhwS3QGEB%~or3bF<|4JXXs; ziO&&h&TWnvKm^Y%|6%A%hLyC$9mBkr@v*U<{G6QRquV8W!I`93{ZYOye!X9Lxr1>d zFWL^pQp?=m4c$<=C!Bb`EB5>uV7>8*48rtc-0y{FR-AjkJ8sp5P7-<)D*!BV=4$DW zkOvsdMk9$tG9?!J>ms?tV<9nN>9OmiKtro60g^VBE{Wwhf~YxKFd!sMV` z*0P^%tQprCE#dy|yXja&GCQDW4I5*WnmcqNNkmpW_vI;P6B;lIR*`x@{H#pu5JDO) zsz1iY1pe`fS2Ot7$c*Pc(8ql=-^QjoFk-ED*lA883E!aGEI9SYgwx%DGMxDq6$aTr zOvjve7aB89W1jEEAHK5`9dR=xGZvVP>i?c@yp@bKB+1|^Ai$F%DP?IRHyeS^c$_2` z2AUuOD@@c*_j#|ZuCtBxB^i6)ec|5W=P6ofz?3&AT5i$atb1GUx6S@cc0ec1+AFEd zR}1epAeYQ-f0$2i4rX*R@tl&S-FDFURz1t35hAJPJ6ghRQ3J`E8f#uu#lA4(jrffd z!yP%b^Fh6g-BCQ3nVgEwF*w$9FbLSfAJ3K|SL3fbJN zom~>WX<&I}(0T9)sy3|QVOW(9L9IESe$#6C-A!FfuB3bgQJG;npSPX3TTIfdD86Lrzr2fv zY^I)b+>hHoUB2<9d$Z`X+=qRg2|fFC4n103OFiVsc>DGikuQu&OR7w&yyDUam+n3Z zBsQyw2En~`EL?Oe$Y=Qsf76hw03JSPs1-*&laBICpDxG|J}X7~N)`|FR$Wq-mgyvO zKds_(c#9q*yAQZLlK*ymSFV4rJJK>!X#{DK0(d0;v8qsZGEC#fb3wtn@jvwH<@evu ze!0Roj5x~$FF#-zRyF>a5Drf+Sz6DjDh$&WL9#u`QGJ*_)9W^3;`6w%_wTIY zX8%0@W7T<6& zU^=C})vOEHz@UQgF9+HkkBnUUJ$K zUFgJZ2e)lC=BQRdeOLaJ6vYdML25`gNXMpI)AU8JJuTKbL;ElsFWD*U+>=33g}?Cr zQ_6ZBsq2_eys4hGk!3ydg&QB;ifmke3i1`grPACOlaTND_jmhZdQq<&kXs$jf))aGYppD~BFlJLG@p90CRU#)`3 z`G%^}Tk)r{>*`v2QiR*?b_6SC>rFajl`0G#7M``s6gAis`@|@6eYw$5CjMv^mX+iO zd{*u-dCe-<;mki|C?qAdmei`F*<*20!mXLrkB8(t$$~9QP+QbA>XG0#P`PiJA!5)w z@Lo(M*g|Idt13313Pt=%_{0HA)c%AJ@EE@@440y&Q@f8Gqd}nX3$%@1Rj=pvsPvd< zKYxT>4RT2prOuFK(7BvxAJ?ZT%^Q%Ro{zs`aR)i?Ym=@(Z)C#dcT~2I>6v0Qh+iHW zOxGyLi(gt99EL;;5~(zZm$W71#xD#T0Aa!By%w^`0~jy)Kz+Ho-hEEUi(Z<1X~ef0 zRax;19T$@Eod#WgSl7HOr&(WpXigBdFv{IdwK1fhPvTK6YPjF~aUkivYN)L%|D596 zx+)ifH*3Ncmn^!wG5zHYI%-Xx2K@ta1-I^kGr>IWWgDhc+1FNvpY)SRew=G4&_mwT zrz-Sez|;p>tV`6kXpm&V zBt>ET#u@|GpAr-qwTZRBo1i|Cd(mU0f~4WJ_gm}Y^`S2rpOOFgTvxfJDMl~8qjwYa zZSk-VGj&d2b>FRCCHJsu^N$`NF2sDX=?oifK3xBKg2<&aMt+taHoNIUC{fd#vj*ixWNXl9@}IZcF_bTwfh(9mbW%jV_28#JL(viRZFj4H$<>qUNK;mo1YcGAaJyLD*QH}@SZZ09+&b{@c2%d(5Ki4-9J znSrIl7{)fE4mwnH;e}7l_O9V+DK<-9d8Wcx&0dwsbDg#qm$#pTQbyvFMF2S66sRU$;< y(718c)yd-J-5PU$?;|wgiud4y_7!#tQ&bIO!#8e8U+`~l!03{R-bWqB$NvH7IHzp@ literal 0 HcmV?d00001 diff --git a/icons/new_block.png b/icons/new_block.png new file mode 100644 index 0000000000000000000000000000000000000000..1b6973cbb9024a69e37e29a5fcd3695674f1d5be GIT binary patch literal 5344 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 LYAO`TSp@zIN==lO literal 0 HcmV?d00001 diff --git a/icons/open.png b/icons/open.png new file mode 100644 index 0000000000000000000000000000000000000000..c9802ff56588659c4b5ddf5660fc25952ec2e877 GIT binary patch literal 720 zcmV;>0x$iEP)E!)(b`QL?0{O0%?HLA9W&#MGq3N*bF&yMZt`)Kmvav1Ue0wmfZ++g z$&SffI!k}|5XHg*&v*x%1LAM{PILFd=Y2fY#!yfUzc@O`Uxfvh-f1vA!S(EC^5e+J z5Ua}#dXgzpr+g|K!L`mTi-iU9Z!(bK3C0ii^V^y8#|bNj_3{haTT>(wadw?;mS;RN zTj?*)b!PrPpj()n%KHGap8&XX`S!p-v9eCGC5;!W@cU1@g|nTRu1`*Uv`<9@%9}5- zQPev*m4D^}-0U43xc_1~yJvS1>!21?E$#8Pu3NXhr#11x|6&ot;`}U+AN~3jkaMSV zgEx%h+_}_qlFZy8VzeM4lv~=VrF}L^hChD!6C{oi;oSJgII$LhB!HVp_5ASZm9J40 z*HJ`6(2!cCK({-?%EO;Ip6WtGP*qmu|D#bZ0Av8%aIa4KNC#oKgV8;(9_rVLr9DQ^ zk5aF@R7+c^4FGNKajX|NcKG6Sj975piba72)rszSm#h|7wcC+ zBSZ`)o@Be@D6H|J3#^nj)-r9$1cHb9HKJe#-`gf^)NmpX8#U0#2USEI;;ji@Z7c!& z;{t12&mWInO0oDc>8z447tpyc8FbF0P%A3yt@-FbC^m3K z&F?o+rgMW2fUAIc+v6O-O!1ZfUGCn$v+v1&GhPD#qb(M^(iRZ_0000tXLe_wFS}oMWU1=sb3!JB{l;=hpp;UM{+qNl60%|Ax{7u?G{X#hY2QbbB?d(Z4;IK^4& z`78zvH`JMII(gaHoXd@sR>$jB#m6B`EfwKUK9Y#^Y6I_xIbEeiSPFictgOiPW5en- zOO?`#w2wJBSrapqh~xE5EkEkR`guLgL)qKE?IwMKvDwwk2<`1VUc9|%+5GYT&B5O6 zd|i{v#p$nF&yB*yHw6bf4V$;aC!U!nG-hA0;yws+owel;mA&2vmwvIli=D1MeidG+ zRQ(JX(dMacd6ez>ht;mdL^ztUux0Bypn=)`77X7OWVd~59NcTgzTnO81i%p1^q0+> z2Mz12MpAl{y%6C{2y82n~txBV2UqB|nuuf~e=|X!KQMh{>OKyc_h6ntw1r{@jgUN7lr8qFPM%12uL64AWw4pY6VJp;1yI$WC5R*dxY_0w%8 zDI$x!b9!vMJ9MfRpS7dINBc_e$D2GhRkP|+&^<+mf3R7bFOo_66Th((iC-sRWr-PX zst__7H^Ibj+FHH4hUo&`57Z=DxcksbpOQDvzz9r2+3H&P!=HAA5+~`!t*!QHO9tFI zv)!WH-NuX?c$-3}Rg+zn4Zf5IITz&*BRX>;S{ro3tJY7AtwKd+uEPlYMnswv9s=&v zH-`S)RYmCx6das*A3mqg;HA~kXgwkVwLKvFQ`5|txJvGv*QYo+e3@i1$*z^zbV5cd zXYhny8YNhirGF}Dy?E+#%@y=F%QnoKQ00*dJ|kxFDxS1`Z3X%awSDZ}su$M-&IKGM{lbKY@<=)qGRZO&OtqL+D>CvU>xou$!_ldF3lF zb!PdyBye zl!_&33{2P&8@dRlhJ?HX;Hcd{VHc_DheJIO$vw*bvs-gFvnlE1vOs*X&qREDb?;?- zpcc)>L;~#t-{m4r7J>_YmNXghr>VSJWlDm(?l`A#kRMrMd)*z|G}zZF#-`lfcMaGf$s;UY0XNi3k*#S z`)+)t-4VW;#IV25Kw$yPQ=?6jApu$Bm!#XD?iuWwJhd@nfC`MBu<=FFG_Ge=)?erb z-U^v)6b#?5F97(f7$C%6kDddnxRCev@azAW_p zoRw6{;is->#MR+6?Kb_WR)fRM@JK_QOgeS8A#3*Fp3%GN_Kq)>qxnkyZZN^-tkdfu z*+pysP@Zk$c6-T90Duo&Bzx~);rq`}G@Q3D-x&DJDljLqW+%A&)k?4Vgt%B*EJ=cR zQBye!-A)s7ADC6%pfaOiiSFp1pTs2F(Z6-5qgEc(`e>#2={RSmFsD4t`7 zh|{!-`4iF~5?<_02Sj|?zZAgh!JEdtZvOqJS$FxZD$K0N12tY#@4qda9Uj0A&=x@z z^Vt%W$ejJh%UrjAKQ0uCrN8en@*d6-?_fzU6ZYg)Z8jlT2eN!4?tYWdpWVtVUmMX| zdYZ*ztapEVrD$W{SwP2^vc>!0*I&f8(<+kNi1T-E{Sy>^M-1PVr$MsI^v`vYU=uC& zHSn1nJJ-)F)Yue=dmPhjy528i!Q`nER>1E7I3Q%)mC@_E(^-nKD{)BPch1C`&5_x@ z@aH)OR^->1^Fdrc8zXNLq=z|g=@^X%8V3r<^dIXeq|8@jUCkV|2+^+9CTx0xgx^I; z+Ko-(K9}hNG>Vh!6+%RF#s1WUK72i{ooM(#J8rt1Mwc5(6$B7Tw1;fERhckXzS%PDt9mZU zo9E5!XZ_`q9M4_}eFBO?WB5+W`J0TTe55E84nAo*UnPs&O(#4b)HQfr!b6nkwkI%3 zHw{5~Gye3s^Bm-H2>tHg*8OWYx9Wt+_FPV2sn+iIH5R$Vc7y28cAH*4?Wb~M3{)B= z`|T%@+5;WN4ec>HDLo*#jM1FN&J+f(FzSh>U=M|od zDodi7d7q}@VEp+S%w``IItu<$TBBGsx@)G8I%~nAC>`;3FFH}HCD#kVXa&2VU@Yb* zBwGm#6rl{INBF1V&&s}FZ38*cCgfG$T;B<|lYgzofjdsUahKc<&Daex=Z684UtgyP zi`RmkpUwxOf#PSA3mL@dPIjaYFGA>oH%TvUpj(+=>c07?s2*%Pzv z^3R*4S#sAh#DsnM)3W@G`s~^t(5OvnkIr?bg@ZaaCQfV_OsN5Cgn`mhO0a;%$iH;l z1I0p5{!0~On|&0N=y{MO4>*cq!$@W4hfIGfmmkUphl0c7A`RrQqTPEL;4iChFjE|1 z(QxogAZieW%AV--x!;|*X&@bX-WTn&ifT_FZ{vQzeE)sedNLKEg#DKUV;qeU=L*d8 zL$md3jPY#hNr$vpzTuO8+7d#N={rbgcaCZinLd`)wGJHP8T4djeIdEo~+zQpj=EVz&6`_G!l2i2o5$rseMehSRC7+3+oXW&}~bm zy*k1~>wnix)ep(i|IN57NgF8Tn(sfd}8`)!bjURI_R_^PPt@2DoEdLWh1l0jieuofPONu%X4_xkQ&NFZ5 z^`jcjBMe!KiI(otkU~Yw_IhDPzjWPXRIs3Juk`>Ju6$1ml$?}`QTEc_OTIaVu3DJ9 z;}FX^z2l{RP0SE{{M(-G_YF<4$ z_s{o|0{Ko-4>&#Ua99j`!!K5@ti_HCd-{z*!@MP_4+m(_g6B`wczGd=_km`aU~A*? zyJ=xEmnqzyidpPZA*}@2cEGBw%I8=x;p7pHLb3!YcF2K{_@FUq(IiWd>e5 z7y%Y4pW#zN{aJG?-RHADfxOYb7TaELgK@MAe&a>-Z@@6@Sg$%iJ)`GeT0c>44*YwP zk_JEiZL8gZWEpst7Qy_;EVxSqksDJgq^%fEe?aq0`Zozu>%j3R5qESVbftC@9q;hx zr94-dJ1Bs7D_);se^4Dza>Xql(n z3N@vpP4)(4+Z{`5Y+vL!kNZ9ViqWTr6i+=ngO@g}rr4P*|1@1Z&1$UDFz{Y)GP!=Y z`EsMZE%f9^-HaRsJ?2xa(9;;jo&mYx;iEi6BLfS#1hLL9dC27=K-V>*3ik-5X|=lEV-BEWja zw$e%|7?Gs+Ht;+q=y;^B+qdImrM&cvncwFrB2WS=`oOKmEl>zxwfL-oL=kdbdQxa5HHKS!tjW#iH5)*G8A_-V8+cR| zTVKN`j&R_taWuBe3pPDXm2O{hq__gIDW|^WmwgllPX7X(q(-1eNHo~WMBm^T2ORr3 z?e~?@!qNAvYP0^j`^yVDw>{H#bc|iiV&=b*)Q7BIIS?!Xyh~nCJK8>S#$r>3S6h|w z-C6uYHW7@0AxZP$y0YF42B}i+Q)T{R;ZGK?=-VD5?4{=$F_51u{UZBW z&_Ggm0n1|f0`Xu2iii@FU2ym#P{uo(r+-aTzPcH4(y60rvO4c!J)p3m|vkSeLAl}(9st69V(kxqwIZg zNmyttciZO14c$z@;g{izz@t=AQfgryZcTkRs1!nU)J+P?{cY2Rmi|z5%uu@)I#Q8^ zh52?5lEQ{=^K_;7T@GiHd-Wj5d5H9{pT-O;s_D>yOZ9nN=s)hyK(fAv!u(7c-7g8( zz`RS-*?NjiSXE$|2(qW%7am{uWLb!M7~q`4D#Ub+0ldV@lZKtJG}Qsa`8?ed;k(A= z??KoCHJOBlO$g5w62<6rq`f#T(1@6yZ$f)=_wPLE z`5`{V8Vcfv;E^}OwtKqgNOg=Lto2AuH@ciT{%#^u*bzcpv=4mUEXzwJS(vk6!FxzK zbng;&MA764B~%wtElN2`2T>2xmZ!P%urUW#v-apK=DYiVA+TuigL$*1`0D~DUAREx zA7a|6vyb~+&*}Wfy1}YK&8!o3FN(kp^VZjaj`3)Bb|>3zZ&Y!n>Ww}X!B)%HU>H3; z|JM;+aMQv@FIeVs2tV8~YM>0$ncKjDg}Q70M&7e+BX=}61UtlwbXkFi~yf+81B=Bqg)~?J5QwxU%Jj#^CWf=E$sr*dC$~doK3JR{W<1EOkQBfy0fl;?NSP!hSWxqWpgSA`RXhy z2A;!c+pj>~NNY+r$98yMY&G@k8&?z*!qdh6G5tPQ36?4?IHa1dK@@t>YWuBjEpiH@ zq$UyB;E;9WttxNLBr7X|Sc7EqHM`+pzrZ@bF`jZ0R;H;AvI#1(zHd^^8^;kvTnGIS zma<)unL5fj!n|v$S;0asc(`7Ze_p_G*F{9&zFH|%)Z(PgF1aM6T2uVuKR=~3^``Mb zU4qStQ^_$HSx^&DzhZJUC&lZRgvhq5$xw?suYCX4)8VbY&?&`h_)q)r`H0cemm!-+fFusJy&O- zqr`B#vS2EUd_9yZ{Zz1ZZR?5>_zQgaLOnm)Y)36jMO5$ZgQzpHw8+kUDur_Untkdp z@^^X~@j~ipKn8?ol=S)gLDe*OaJI?2&clVp zJq(JG%hXn|g58%X15l+L5=9a|W(BEk$)^^jV$R>df_h+`Ib4D^ZzP%SS)69u9N3qB ztln$?!k@OS)MZJH6Z>rRK?+fmQRURa-?_WAZ!zEE^`eZv@Tlz6n)Xv=XQ1FPQTAI- z<3H-y6pRPh8BD8Q%?D=~^**4VVr6!(D(FzBeMOC_(&lIGA0hd^Xv>V9yQw?6$)y0) zL>|W52{}J@;M=8MB~kX>pffwdPH^h3UWLjTOG%z6S&{}3?uLsc{(fOKeg!ZJWQRjz z{!DnIIFs28HYs9c(-b05%ZPXG=rtrm~1~k&2oCkI>?TF6V`>`RC6y1a<&KO!y8Dp~5CG zZ%}2ywzTlw$OwsPX#~ow|AE>;6NBOk8k;f@26Zu=pvpy!KfD&883FPU1EQThz`_+} zCW@FmBId#eXwIy%zww6+s)Tru*zfE>00n} z<>!l5pJuHXE7(>ulG86-#*xfzP#wZQ0$xgX>D)80Z&uo`!C&h^bZKYKhXRxZ1P$vG$qd6t z2fUI5USR|QxF?JTGh)L>Ysk(_qEGX|X6qfLh5W2;`&w%gK9E6!Fxo76Y>d-fjET~6vB?1SadR^KLep0vRBI#TPXj2fLF7f`6e_p*!Mog5(u0+_}Jd7cFvVi zz)uLCn$znod4w&Z%kk%C*yLM9b9K9^kE9AV zMK%#ZKl50|D>X~}A^hoVfn(lc6f>J}u4x)qR(yeB&V+i_E31-EhAFuwbQd}K0Wr@u z{6RUohU8_KK6KKP4Hx@6M_S?E78#T-8Zg(#Yg3JPwQ&TlbIKT0z zs}0Z4kcuW>H}3ObDQf5oA<$P%$cm+!&%&{!!du+S z=7d2g24+(vkNlylUq23o+I))yS2oj|h3P~PDyrT8w5=kWV?^0Rly&h7$1GQ~hg?v> z)k;H{vd2J&gD0OB2l`k1G;(y8{MR7{&XUiPp9bR4Vluh$*#J@bdNHarBAajnpe@4- zt{HJZk21Mvt(}}0GtxU1q8`Lowt4MR8FZjV?ltt1{gN}w^u6%`-QO*C=ehb;Y&1j4 zbXx}1E_XPa$w;g;gBp=O*BPiC!oUezf1gC&Z=Z)QyFU_IV=GIa#6R%GZyU80bP*3) z7SJlmwCTGgsH!4ULNl};JoySJV#QW_l~qmj;)I20+DY3cjo`H&phj>S*#|g;#Ds^4 z86`wQp37kfqX0-Yx*jb<1urw|p}CV>l=L)KTR3@shwj2YEv2oBZrL0X!fUnRmWOE} z@NW>eFBtRr1MIx)h`BR!mShX_wUsqE{Yd?L9$D!mer0ambYJhjir?Y5jQV`bCZ%PY zPx_g0ZO?DzPlxDBIUfKRTGh?abal{$WP2>JM+6+{7)nX8lQhI8-yM_FZa>r-^c93`Oy-3S3?pVWG)a! zhv!C7qvV5dMgoq>-Zz+24>2@Krr?li1?u`1EvTDy7o~%UTSf0}4^?1DoA~#^1xAmD#=$O-&V|}HC`;J$(OVy+1k`j5F>=Udq&u8XV+|z z=FsfWoM$+)A@KGY?j*)46I%~SDl_vJb;2)DFtF-eQPR<|)sdk2>N`GX((*tnv$n^z zA>|E|OA|iIEz=LU<4QXa$Kq3i@A^B_4`Ge+=5U{$=_?uebjr7qGhPvxIW4c9nq%~Y zX6a?-;V{tc*xY`UAkJdd;NPCunf@8%B1Xj@1QWbP=7#2Fqs+XK0f9JQ=ED;JqxP*WSs0nrb zLBm_l2xdO}nE|>9uGcbqtZI*=9>-*^?MA<}Go!tzRRGP$3IthS5aLHoEd-v-glSC) zIXo_{k$z)Pk^vIW?ZKI10tuz~R<&4X(B)+^JTuG0F6efFP-q0C*FMcc}T}KwHvj=jnyKZi*Ll>W~TbhSB<(`rm&C9Nh8Y z8!Pu?rz5;Svs#cDU425ENRyBEL=m5jX~!kt$Ikmdde@oD=e@u}cPMXclhFirA`TbJADb)d+23ck_aCW$M>eJts1_)7yHE-VX|XcyKv->v=)WucX_rwJpm+mo{@A=j6yfL$}!~ zE-Pw)K%mYCs37Y;!pSA>XY0dT};7EOSJB*lRS#KonMp!WYC^ie&^+mYdZ zyLmU_GlM3OKrRwHI#)juV;(bF#a_>^kN}6k6^$*H9`Y$JWT~Yoci{0QVIrwS~%^V3k{VU0H-=Z_p6o3XaKxv}6*|sSmvcEZ(rP|WL z2Gm#{CPe-jLAaD;ECQrIeG|yNT4AqSQZ>`su@tiOr6W}LasF!|@QoQZC zI*E2}7)dmbj3jr=xi63XENf!S-B0Nq)0K#)i8X7H;*mv%E#NsE}NhI3f^)b*G24m+dv z*w*dg;9;HwlkfzSqqEJs0EH|s?dcOS%-qItct6`yRcfjxE|!Fd`Q&!;e13?HEsdsP zLQAX*U!yoHl}9gUcV`3QLbYH3O4vs8ln%l}v__)|n55|kO|6r2s%VwfvC^e_Tq?k$YX!^oBjq<{nSDxJeMm)Y|N14@S=ItaU} zCSjs6R~$>yLmH5dpz7!c=PY67>oteEpFyDNW9IzFa{kV{UuPcO`uK602<=khlO1mQ z3)y<&#Ybj?h~oY}!%XA}wBAEW^GgjL1%=Rv5p_nfc;R`s)>A8bYjMEH z|m> zr1*7fCl4O9F9AJ%_mU?ePA2%mN55t2?301aq{1pR(y^yJ$(OV$Gs zwC*m|ee_$tt>m~k!CTi`R1K;2!t%L2rP?UwsfqnfifR28 z8rh8debkBfh{+@&(I6~msVr8Hg5MgD;8c1|$fa~Nb@|>x9@>wtDVpl+d8Ryi^+5$z+^G-Ym8Rh#faeY*YScl zSXCjI>8D2o**DtUJd)agS1fL$0{a*D6FyIx!jz5*tp=hmumY=47HN1MNR&vy6RFjK zR3>DXQTX7o&Uo5^i@c_}S-xL=m5wvtN|3)-zi%qVfmL)jfp7&f5Q5rxh2F z5~l48J%;wz^**C1<`-up~OKQ8)pPo$t+Y!*u~I`3H)g(Vjbuq zM4~BrqL6_E#~u8zlV()lS@(%c89G@-w4yBb6!OBvC6xh0<9nX->~|KbEpOT~tgG+B zdKIc&b=^iqop8c?>m9?rKkHDdfOeYI#Bf@0%{y)w(N7MRQ`f(!u?bl~DIlQhuu`ch zlyCjj x%P)!mtqVtASPt`krN@M?)pwHwhtLAG~zy0kA`gIk=M}i#NmK`b}=gg_N zGjsiRWLtj5>Q5A*xUs#ox^pV^8E+yqZD3V&@0i&EJ@ny1eTC*@R}taobf+IV-l3o~ zG8aY(Gym?C53ws==f*z4VvwI2Zit!YKM%wp_Bx}CX*LAUNd_xim97I$?i9V7DlJZg zbbv~(goZnl+(0ySmjjCd=I^qw23n2*&<>A@?oXJJu}5a0Cn`9xo=rJXD(T~_@onbS z{o)U45j{rxd0t}PI(BtE-2~Q!aoq$2bl`;#|JYx4{(QnA%-!)Z9C1+<&M~SgQ;knn zTM33}lJul&?S#Wj2GP2+dAHKL!Ts`yy0(~z4hl12qrWqsgz>zV=cP$n%s(5 zY{sb59G|H)dQOcXK=Dv9opV<9`!w8mynY(=g{-qtMEOrumRtVzppMVRN3G=iOiL;p zso=D?i=NgIrC}5CutY)3>OM|5Q2_bQr2FT!#6J$Qffm%Nao4u*{NNib#`5|Ty=}F; zMQ+A(_53L*ZgryrV0UQ(#LJu4bDijSN#u`R84GSVC*M-{s@)_WU&KK$jswSz$42ku zXs6q*rA2`Zx^syEi`gs1a%E(#fnXcxp=2obcJjw#+85}fj$Haw_ZZUqZ^owfVnYWO zsqa-^OZ@2GFdN^_in%z}kt8ao853coXI=5uS5djHd^xXnc(ukD)0kp++A+Vpvv?sd z2SbWyo1b$BgYSJOiR;0V4naS^cu7g=wLqubPeFLlislvtCg{(_)65%rq&y&Sov@;i zfWF6)SnI-RJfxz-6u-VU&qepqzTZYL>BpDfqRz+6Cc_Ui6>LGPQ*a6%A9&9$2(mf-;+Jr#c z54wDxv_mY8ehFpX2$qsi2WYeK{;_2nna6@QtMbX@fvjla+2kO~k4dj~PFUbNWEwp{~c)TX zb!Yk>rA}JAsv0iHMRWc9LfzP`nBcu`sxmUwE}Itl?1ak$6WYNX?#&ITA5UaRVs}D5 z)WtV><^F+%9zjkce|4sWO0f9EJe3osys7-eDan#%+ov}VpqZM3N}Pq#wJ&|vRvg}$(1%$OLQ=K!7;(T*YaHNvEgNd;oU+W3#-t&MB8WCVqsm(#1x$apA%2VqTe>QJ`e6G?N6vyA*f9 z-FE1v%v$y1qY)q3<0N^cpu}A34)D-nI9Hc%VS{Y}6S_{hvNhCb-=gl)je`7?1~^$0g5x!CW7$r5`JF)04n>&p-^G1ZmN3sZJXhHcxB z?X+pTP*2vqAnBDfAeKl$bL`8MK}yGlUw3_3u>Is#{ODH(mRF^Sos4!il}4gqf-Wrb z(OySj_-#zpMn#3QLs$h2g0uT^;_fCFIr1sCZkB+>0kWbHM~|>3fwN-X8?{Z?KezUS zYPr3A{vagqY>>Y5lYB=TmEDluA^M>GQk+dbgov#+K)a=j-(o zeEl;4SYIGL((l+7`wv|Ip4w=-D$s-c^pu$8cyqcyfs7xaDuK_o3hiCPcEw9NAJdoA zbT@_nc2$vug2I*}-c2J%*P_d;{KI9_xhP%{bN+7F7GS4a(R@$@vFDao7mmw7)nQ-0 z977Hs=%-?$J6eIWrz9)d;L6%vLU<;Y{+*~DsQ@1CJz$&gbUED_{f0NJynr~}@sEgl zJu=Spk4P8G?qVzSt`a@B+Q605LEpW0Nm{x}(yl5hQke{>^Ocq0;rC0Ka7JW6e>iYB zupGF`k&;j^tJ-i$-Bzzu*bb;j%D&%gx`O|Ddc47i>=HI z)T#6(CL_(Zi5R1r`C}9ocrigN2VQ2QAt%)n2R~m&rEwvWG{OYtp(KNsw^nHElu3a0 z+VdVI5qI}uY0~ySR{GrA?Tkd)-CDees`>7|OJ3@RN zHM9?qAYum}=#N~{7 zCwe-7fDTDLWC}DghSjEF_5ap&q{+%i3?kOeudBFK-)y0}_eGDX?P054NJ>&6JpvHy zm@8l}Iy~I|Z@!w@%;ME@Bbru+GVYH6aWuD@bb2_Jfd`g=u@ZFRYWMr53}O+1fmYI? zlKo;&Fhrb_Yn9t8!}J1l$3u-0zvhj#dq$^5MxFK?c_{Tc2#joPHsVsH3WV-x(w0$k zC`0y$m2&9gAi0ve7FF2b8^kpcAnqOXd~Ww6*y1?YhYNOYV+Zn@r8uP80kxQ@g1Jif z%%CT4%%FuXdoqc-1jW#KvOMH=93SGjb=bxLYW@B2#GocNWD#qvBe?*i()6y8u8vCC z?Na#FJJSAhI0mPuZHniz`*B((W<++ScP%^NnBuZ&044_9 zVII@a!V?04ZJ3RVjm-yD9Z4jCUrOcP+EZ1t^Dq}xwn6v4n7Mk4c* zpigXvZz_^XfWy~e_XTlZ#^t#4j!@$!AabYM9cKiA^)?&D2dtvUSq(!d+gz0JHW-_JTV5019lhMh+|^3n|P3Jgyu#_G|_9{f1BPDy@R8cxVzJP z)St3#sfep1Z`wBvph&I)NY=v8!-b;R1-9;mw1yUA;MkzZF_kp;J{ymRs{$an|v8O4SA*cr90k46`4tbh&&5X0kUi15vQXOZg^&x~8=P~mmS zx*=5a)acY=mOj>@5i_R?9cpZ+v8K&`eES63CC+xj6n^{INOOIr@@;#)Tr##PL-LAK z7%~nY*_|)v!q(VN7n4J09yt zZhsSiypTuHtaB~ivDqXf^pWbccBy%Mu${A+$tkB43_0l~p&g_n1!8yS46x>B1U#mp z%aA)h*RalV{BS%~wsaNK^nssK{FN^k>_qLmY@_+Qnev=j<&e=ctWa3l>U*bv^Ao`^ z-6Gjx-_1sn9$&C!T@1#boGyGRio~z%YO$oeJoSnj?IOO<#p8cS<8~3aUa^t3Tw!cx zTQp?v3wYLWcMQ#BJ^~cV@nn7I2HA3iD7!LynslOPUOAp6&9*AgmOHDx(G=Fs((ckT z8f?48k-?MC7zaO~>FMhH5DUAyHsJ$@rm4P8LxZ!IReA8(hyhzJ-hMqGupA$C!kPW_ zYfdX=g~DbOlkr4!Gp>*qL7EZzDg*Tz{bm-kxwD^YyZW%&OqR#^16{a|5Cl~=KRm3Q zq-8gwKVuD)2<~`kfQkx9oncb8&hBB9i;tckGuNzf^qB%Cgs_=%rXqfA3h2S0&7$h=0J07iouM94sEjAlUp^URaHK#sgg8!(4SxyEz!_$9iPxP zQ(NzVcJm%dnXPDf0$BQ&k3T#3ATL?PAq1cc?1Xp{i6-{5e1)Qr2z zfy)Vs(_fN(42}3mV)>nIRsYFg47u*X@B>u^c~K}V_mYeT!YM!9~kUeF5OvL2;W3o$rMw$?#W#^ zWXT>RidY>drV&yr)xEoIr&%DsHTAEbj;KNs=r*Lj7)!P9+0bHA?n--$*l)U~ap{YM z_e;B^53qE&N`D-+)Mg05HPQs8H%8HadOfl|QY) z4g);w<+sxQaW2eIDf!oauK!&A=h{=OSm_UIsOz|y|V#A4S*P<`z zvuKfId$M!vh_(tHSo9HJ5AujiL88QDKWvW1R&AWz%xc8d>ygW?^xnLgl(OKJl}=8v z>E=fw?gYjnszxOjXRPD)4dWuX_53HVU@A+fOmXvG4bqi=g%sON~4&YL~yQM_Ln z)}YUGRQ%Kyu{Gc+=ipXB_}(IN^&Id!qtY-#Wq9vcXlmHR9OC|5v)!G>ZL6%nzP?kM z%A9ok>HxeJ)FWjw4S8JjyQzL#amaASl9lNz<=KQ#f?Q2mP8!Q2MUga?*)h*IPGJRY zaG}tGCvDzgzv5BB(Z)*!S#}a<;7WNUdGu`|$klpxvXsoW{q!F)SPWJ*r9;GjuPKri zAf`h;h6*Ypza{bj3(!wNtFNdHWv2R?uYYP!!`L5mFBy8qJSlwOvECowVtGm+_3zO@ zYe1L%)4i)0lbIOZ1Gc4xpr;2r2b2e)t>hOO`q~gww0hbgIZgBOZZPBK9mNsWtG12P z=AV=Ef*;DIO`ibu*Rm=$TcpBJt1)!q>mQ zf#jTGrT8J+OG^?=Xr=!i(U;Ovz1J{z<#w><=%%!r`+aXv;5SV$jS)8VC8O+5Xu#D- zp7r~W^A)Gwh_-e4&lhcd<%E%{hS=*58Y`y|`hM!>2Ur}4=h`>)J!*jzTT`nuNzMpo}#sRNefrr3;KzJWj6kn?JMUZH-z_7;|DYlaGm7-|JuDVs?U z1$I-7RF1IAvNsbf+t4#BY7bpMV{Utpc_ ze-Z6Fd&{b1-afH}6__+A{5L^97$fL66q~3LfB0C>IPgFFp`^oqh zh$})d zQ{Aq{9DpHB3O{vjki&)8|14L1qHQ;yR(;#eD$-qiD(S6GGb*icn_V*|9JR;H+g!T* zB9E1;o{blHLkjBP?9lI}1^Rkl+&wze^Z2{G_gS+PfyscLW9lba;T53SmzC7tFP|8| z?(SFn28817Q5`ka^u=&rEBZQ^!X8eef?4P8MhT^#o8$pz6oFtsm%Fj0FlgFr|HfU3 zZsfjmUv>#u_KA*1=->GzL4=3VEtZF?qB@rp#r>u}LHwIFX%<<=hfiqlRS_al7~6WD zJWPdL*Xx@m=N3PaL}3lo&3x`=tG^C=6tf3>z4a~C;chW;{oan_Ye$y0&PDvT&3P=I z%M1azc*@*H7C)LolkxtaXn~-D8#O2{7NWDgsy^m7ZuHiim3i@_$xiW~nA0GYNz<8# zUr&i_rARu{lHx@Z;8=&(sxyWWxfOy4N+Fg)8sO|+VNNS{PvXV(sA`)9&k5?@0xB9y zrvl5BbIjcoaZPHIGr3r4RE> z?&c&%iAF(s(`K*aE8qkz?8q)~$F*9Cv-L^W%;aSPY-$$mwH@&`x2*lk2J>7Q%X*%%a*u410;XMSjUPTtZ2{3=E)#Jx3_3r0Z_+dR;q*BwQ%L!B1xRnQerzyK;h(EdVW3rMf?K zuzZZNJ$DcDAub>p4Q2cZ;SCANoMrbe0`>{^rgwAz`;#+~D`xD&DqF1!f6{5x!1{V!eRO0>}zIA0VMc$fdXt@SD--!^gVG zil^FG@f<*jIw+m-Sy`lvH$qh!A$Pn+XJddPctW648IyewvA;jXajQI8h1^74A5#RI+ksMcd^LHNy> zsdFrPkQULad-<#y=QEgkb;iMTCu75TE!61XZ|r%+<^DuR06H>F70;H1U5ma@*CACC z@Py9?^(}6`-~NkF7^M8-as$|eHN8ak460h6ZcLh*TuteC$@izxv^IveWmrZs9~J+ND#4Hga~c$q^Cc)sZb|OldkHSOds!2Gz}SOhWAEprgZ*g*mO~K;3`hp zu=2t0F7G4LsYuJuT1#;F@!QOVi|U2oTLq(g^elHx!k?W;{2=2o*$WXMVAe=lN+HXixJmX<)dd7 z!Ox4F1w#ErQ5ve2>qR1(emNsjVB;JbfF0aoo_(VkAo|6E(Q=k&K6lqFOj$fES`#3Js z-mj+T$5Tv0<&9#^6I-s1OwbN`ACgl+96Pe%+YFpk1Pw z&R$+t=SiL8#U5Mb*^V1wCy%6-;*+&!Cw`^=^yFj1$g1pZxKv)6&;${5$v9|3#y-9G z@14LKmDSiL1jyuY*j1F?3tsutTk0&7dZE{Z@rfT@rt;yj|4K=i^PX5J{nx#+e+|pV z&yX`_VFlTM)Hdu0ODs^ekH-ksa{FM?uTV{fE#~W~?M9vysCm8ub0mHA0r7=ZSZhm+9&Vaxbx#)e;ahg!>C4qT%W7VsROEsO-3cTiRf zsCaB(y|qDD!%U(z-loX8YwBpwld289W`;BhIwK;1UKtEXQkAs8Wp2JdeiP=G+$oLb zY>6~=i+cCnDc?o;y|J+!dV?0m>}D%AKE&T*K9J@;3f9ddz2Q895I_)Q+fvO1TA{#N zoJjw~p=79=@C^R=)>}pend|)>i=jbJC{*Xhdxt0bC(-Rb6Q9!zL$L35WS3L@b*3o1 zf=T@7+fRx4zz_F(184&1O&w|BR@$G`SVUJvbuZ0bd4I8GzHGPl4C1`|Yud#jVacZd zB|t_3SH42vA~Qw?uKR0n)`1Rey9YmZaiZIo50B(zdana@Jnt70O}p4G;gg+!g!hr4@nA4X2&{WVBiuLPiFAVj9V=$0Tc}MbrN@v|oQIGEO zR+FY&334@T!e_X4cUbM64iqc5gKf{x+@TQ+M@?1jmF1{3Th$&-w z4SUTP{`-=tkG_K^Eh+U`--SYq9-mt>DUZccZ3~{<02moFFumxA ztE$OrM^k9INn|TE(S0ptnteGyU?Nnv1FP$$;6zcYF8}Hd*|)%-vt7xlwozWZMY&xU zUSdesHN+%4$cY1a^5xYz=)zeTwRqC>DBixiz@*vyU6A3K$t)~W{%QM@N^b+0MTqeN zz8--M^r6p`@KL5L9d-Cf(60~G&A8*i(KlAY$7}J_UthRh1nLt9V(kx4Ujzj%yDh&W zGg&_e!S*-bp1+Q=wNIl9FAyASydXY#K|hL>X99()Lq6$1Od{jR_`*1ENMTvBHR&kq zd5+S0oyoL^Il!6@l-fMV2KXFXgtKs=A3ypW6FYq-`l(n?Sig79RjnoSA%&;>zbX0( zTg1E5{<)Q!3f;)H3!SUIF41qJ3>g|FgCDD7 zkSa~udl#1v{a{4>aCZXJ>FLR`RP&vqU@(=r>UUT~7uVGNY%!*HUOz*bS>2qjNj+cG zJeD~jgLH+j=YoA+2bv)9-Ku|x)qqaM!W$9K&eRPxZ&R32ADKeIeuDSO-RYLQ)C_3DL?W~!OeW}gl^WL|C5D>5Mk`Uox{L`2rF$N@nd zLXs0N=^BnpCpp#W!4Kb0=R$;hknx5|T;xPpHBT!6(VNY;=-6kDF%ljUl3+(>ujHpw7+ za+^q$&ec!Q<(4Ne(c1HaG`}Rw=L#Y>q0<8;q#DjYmqJE^QU~+J8NYQK5v*qVhmKM}@S}jy|&MDxI zMEs4)yi*}k=dqPEP3H2ox-%ux=ii$+^rbVdD6iR|t8u64f@dR)q7OF&**|_^`S^8^V=mw^kZ0UU`kQx(yPVLJUn1N`nSE(f!+Ub&OVc z&VOv3(};7}d5d2p`;O57>}(|g9+ceCL~ZR-ns>yERgk&&n4@Qs=^yl{h*L|T?-#f< zRAN6=Zd`xz`-ZYviLlu71W~la@mhO zW6Q3dv=Hc~qb!&+{#@3uIdQ}G&r#E^_IhE|)PWr^pF_;zsO^=KP6IBGcIS*&i26pF zmSfiuAiUWNj{Az#g{9kg0>Lk3>5w$X3iKL3eYxAZ3P!UIU!|)sOQ&CivC<)hb%5!= z!axZrysa&!1h7`9BP%5$*xgz&1KxI_`@d9g<#&CJNUismCgkOCJ81O@Ja>EXcbR;( z3bu73X{3Xu`1gYT=NAua?Db;>VV9b@PP?+`C40%J&pe}qCddoD3{}Cdhp)8gEHheuLfgXnVkCmOh<5v_E zVM_vT^F!>o0zf)AjlUsl1WuEq(TP?cuPdQ3PfBhft1a=G-=ZxM-*gF*?*v_V=?g;C zX;~Iu>&(pPA?4FCi)IKC6sC;D$(~QV#oyWc0N5_*Ns_Owde-T)vRLQu+T%l2hl;PC=-z{qg2sj$ z|6(NF#L-5NvhR3pF9=+O+-o7Pq?v(nUOM|q8wCGrjs4{vcSHkCo*J}IqNi<#)h`q*#$!0ktsmX za;_Z)rhyW8EpFS6-5uh{U=@?9TB4e#?r8@2Q6+sjLJvr1por{AmzXwt=G`rJr`HH2 zobSdHu6-Q2BZ3FmNUf!@`@Mm0vT?73C7B5f9;mOMM<;0CZ~;}x>dkEx64ibcFvtl0{*gEE&~OE?7LbniP|IBeQ`z0#1XJ}6U~pTQpA*9pM)k|9Wb zusz@CoTv!#R}5QMKBH;6$v|~sTcaq2?_-9%}EZfsI( zBexMmrzi&n=G(0Xm~L2foaD^!P4%F;s4S7EuYDVQAZ%a;9>9_h_*Mysy?a2AE=3tj z9Q7Hn=p$E6aM6_8k>rE*^$^MWQ73nWdpIv#BQ6v0F_}Q+QG524k#Sp^7LMCF&HKt% zAPh8Z<2{~8Y5c>_?CF9+Q??}%C~yTl0uSzJ14U*Ud^`+D>oOR^w<>W>wT-kHpTTy& z(%w@tsgXdc8ZlxtzkG?YN~^}%=%Dr!&GE-EBp6%Gb$R|lPdYwqpi)-EMRb~t#Gey! zvez%^ybau+5w1*N3GMdpL#4Ig#Xrkp9|S-yW5Bs1bOR38ubN6E#wX^qrClC`!1$J?LV6onssfeffvdnN5XzTDpit{G;4?4OxKH~5s@*i z{4YRm`$wl z9JiS@ad#edq!+swK444O%G*{O4~zaU1Ussn3g3^C^e44j z%s|l^zGdzGA2_opuv!i~siN7DbRnL>dBSF?2?lr?@2WNh;J+TUlZ1e{_VE`;KFrb6 z9IAW#|Awjlas17InFX|G_85&aKE(=sJ}QkJz7VPwBwMborT z7D&E)kOr6oMLZz$jAGzMcD#B&I9;D1|3`9EDTNU*d|{f8X7;_F+dFb>yYr6a#q0#} zFRy&(A;)4{qSZNs2iB+*@k-5nU1QYxj5QWpcWVF^^Pg>bA`qOSR1H4-xoz|j!9@+q z4nt2kuXqC&7C`Z+JM!8ZBxJIhy4Y6tGip_hx%T`n_v&_t_s&DJYt{lceA60^^asJ# zI$ngQJ;4nleiIZ}-OtLs;UyKf|7_F>)|5OVNh<~nnzxsoxA*96JLfJkoTs`kpX54& zepKQxe7MLCFvIfyZ&s#%^BkH4-RLi*v8iFMLof6mNi`UYVU z1JJizh$dPDMPfvC>5rfmVe!63pwz??P%xG`>d!+wDGblSI+_tr;PJRKL5WN50&*bW zFO&|Ss?wMf)|Z(3h70zF3t`|G6UH#Eb@)*ABp&_j`&D&3B8r(%SO~CG@OI*O(F4xU zL^D5+Y&FaUcG(OUPfR;2NiLQm6!Z{xIj@d zhX<8_LHl-z>}g7T#R}%aP+5Q*U}xyQbTn|U>bg{ET{$B>qDOe{h2R^Yq=vbf__xGD zKizz6+AU_vaiRk4+V>+rSF@%sLqfY>y|#^Mw)I(c<_%3xi11lVW<)la@dts(Xp#J~ z+jjI!0O`)CN<}F+EgY)=ii6^+JnqT+FgjLl7QX*^Pekj3`_pau_m*Abz|i+cuu#zc~B5FB}sIYTJVXh4G2sFtWEQQUENneZ#C}1oRNt# zr2bp1F5rQiFS*%t1e`%-F#WUnjq9WR|K&4y3x#yF32wUP&en~~M8*Ip^Bz9;WzBD$ zk6k$KN$OBEM=V`ZtoMJZ-5Qg7F#AiD#2!D;SB6(Ucuyxd!U#l+b1PxoXfYoZ)<>VB z8S5@Kj2SY!sT{0&RghqaT~||k-i!BMzqPR&7AI^OU}LeJ)L)MxS9>4G`<*r&M#At_ zJD8nA7ir0Yb07_3x0P;)dl<$l2rH!HM+ldyzA52oawlzjlyDl&L>SL*qiuDRB|L&5 z2b5sV9!m|=lsCBl*v;%jFzzZu}(AUkD15)mf%5$(K`nA^aiTr>r zHT?Na_bJh0j5gDk*-SetMTK;6ug$N0M94&thjY3TMue|9JAS+zGy4LFvgyRH(BXv4tuhvZr*YNx2YKQTrZ_=3i+60djzIL#6t)Fjh9%tR8(H=f9 zE)Q#`*w=BFX;NIGybz$ffuh~OasZSqPfH5djCN3l9pvj-AX<{Uzuud_XYWkB4`yGi zL_;?#pCBC^fjEGH`X(0{gjpl^tN;7>pXmERJ0SS6cpz9Bvu^f#d-LDE=x;3{^BkqE zUM!9xHiErFktIyTSQU)4RRkY)iiiRIwWdgmZ_yg9OP|aU6tP;Plfdpa`%@y2`_&BL zGO-LXyo4Le@#SVa57EG}U_z^m@Jj z?eaicy8ti7H~u`)0j;l#O)418IGo!igR2S%5^||B<=cT=%4NE7h5^Tb!#<*X8r5tT z%9uwqOu4AxVG0})@C@>C6^2vXz&vy$73*k^Cg%>L)sf6PBzpJbVuQ2`bt;QmZ@ zJirflE|xJ4TjdSMnamI|s9#!>pqgh!HI#|%kn%So@JQ`9)ZK1lMa;9r2L$~_<+^0Z z@oDJix}V_YX5yTFB&7%9XrAL>>5BHS8EDdiYXX_!Bp8(Dx~HB6?#IF)iu@HhzbXPX z?5-2?nB~ZqFSMKkl#{llR{VnGuFxZ5N!Py?S8HYW&H8h%LJDP@=rFY;QDHp;d zK1ItmU14ZolH;NVfg-68E~0tz!yryDlDUrnu%-$ba*hn_6|ULN|288i0(4#RrRHGS z5>$|7H(_o^-C@H?HuN)0=A<|!mzUsZynV5dbKn0_3cAH!y;{bNzTR~F=5V+F$ zduVx2-OcgPJ(p;*AmX)N_{}V*5Ci7vz?FZD`ab|$&pH<3;qkyabsie^YH_>bm*oWS zY*~%jY==t~f#@iMnaJ!1ArUR>rSqeJ%rt{Rcj3i5*AYPVS(36UIx zIs)JaOuEN63C+JgZ!Gab&FaMuPUKeczh#EcMB+~=h4=4v^3`CU6jF6z{`6|LW!-fb z5%`^XA^h}*&&kXKo~uJ6QGu}Js>!!LDNurRe}(s?W`C+!10&NPz@pS?#efqobYN?3 z2|7@ib%nI!9qL!m&R(5Afk9ba*FII2*p8$)uq9KX0XBX<)!ld9I_%5`Bq9$$Bq)dh zgbN`BmU6>bWtB{R-<+6s;>}y{H`w9-o%HO%>ug8~U$4%#!uXDZPr1B6z=r{h)Av61 zRF25(o}#$}Njjtjji9RA_1pz)guu>GgB|Qrv{v5nwL~(9lGR-5Bb`5oPmvYAhqzp7 zpbp1!x4`tx0KsgWGLS?`4HaboWT@bZm3Z6o;{K@Mc&%=We?484Y$SceQiRe*Qnpfq z(g<*ed{HT&*$Y8p>}x;xjxtW9t>I#t;KJEUID zlY0Io{1RqHXyw2-g<1WND6Ga#8O>x#eGq6kGS(5NGk>jN7k|e zD~aJ`aT_o?nn*v|8#DAw#mb0R7&y{gNp{Q5L-y&>PyS9Co7&NJw?JN{4#IA;f^%)N z6CnAqByi!5x_|1`i58*ixTAQM%{ye+G~~T78d|`{sAG7?#-hAV4!7hw*|Ez+ysNTq z;7*9KK)l6Xd?u|^s$(QYCX&EOxk@iHElE-+;z&UO_S5mC@M=LIl~hVzBIMpT)6R(a z$6k#y{Yu8+TAuU9nX!z02-UXW9xQ1i!3G}Jr#54_l{g-GZ=(2@k~ zzWNhWvk)u4^4-pa%m8i-eoBX&tO4I(<=anpWQ*@Sk`g7lcYSLS(GDXErh1i;2R^Wi z(BmlwC_E6DzZ!1)UGx$E9nBX46+n<>#C8z7pYlr>%Cd$~0wY=*H)*(8yOjx`{*h%@ zc_mzjFO=o;*mZx0A^>|0(1N(S!%0aJC`!tqHd-r56>VO^KM7b~x@OO=Rzp^1!w#-{BE>$t)DaDb?xHE`(T9lYpYP+ETkEe!+cQOrSxhPF3n z{#MU{%q!$8dXdg83gkSq9zC*k&&Dg5_mg;{1>%1tTzMeV{U87CGz??R+*^83J+Y!3 zk(rW2My^&3QSKv><=h=X6 z{Jh_v_viB`@Gb(^rF>r?J_~2&z_*{+q7~1E6O50M=wn}Jcia@qa`Hj!E=m9=q3nUr zGHV_TgFiq2?KXQoZ}!m(oH%m_8xA0#u;`73xu2z*Ku@8j#g_1a=1rdxxf@9W> zpO&Hebz44bX&r&M34{m#1YV1qpQFX6vi2(*MTV&Y-I5;_mHo#2 zvVOa5!-#!5?G&ytO?xKmlP_nOs0VM6)f#R;YHrgv5`NGIMJ{>=WM1Jf{@SdL1Jkj|I-?}Pe2rg19{2CJ^!t8{KNYA#KyBpt^J>gB~oNUt0`fAY_WP{4Mwm`0MSYt zKjTlgFkc)C$T?o`Wk`28XJ8r4QL#GB8Z0ySiH*%f%#Y!hTOjyP)nnI0yBZsnI~d?w z7WXW#0fih$bxd;-WPiPS-F*im#z1!UTBiiFDRn@7F)<|c_t#77YZ4V_369dXWFBcm z0ytoYr(e{+{j^S+1F_0OR(eWJ1#7UVMsGq4j?G>lLNz+Mp*2)RiJC}9PMx%?J7aXWX@8Nkv>iDqLm9#e(H)pPXc$`>b17qB$bC-|w zheVCYpOz%3%8*l7D3m<=G{jmGHWoM8V%}{s=WK*V5lV!U_-}?gdqwRbU_E2d=Z5)W zhDn3JG~AeBoR;M87Sn1Y!{LX?x=_A~>cxMm^yv2@ZPz(2c8SCX9FaA!-emn=XwIgU zCA^}k^eW)5S$T+lkAJ5DcY$;o0EH(1RSIm6h5_dKf{#*@I~0bz`;|OBx@62?`ME>! zm!6G%mf)c+9^gDe*N%PlkDt4J_6(~K$7GT%MrKJUPdqO#y!Z3hM^Xxg%2hP(ic@*D zmysP%*rCUe{BTKsVs}U9b!5|r3Ss(#b zlZI$mAl+Hbnx;?p}uhBjJa_50ZX++c!OH$3hMImZ2 zbBcx9AYyLE5uoP=tIX${w?Lu5k=tfvBV!h;9{LidbTz&e&JV6)zP2H<#rVC?7Fc%}gNO!UA;8dl>MP2{t~o-rZ99KCH;03|6oUc_#M$wj*+pnCo_EP{eu2Sf}{K3Llr4GofhUtaGB338LEhv|k!Cnjhf1?%b} z`a=X~2<1?vxXq$7KP{dXqmZt&<)3T{oVhelsGJ!8nobODLVH>Oxr?K^rT3cCY9k18xkEy!CRDS zH+3v*spp;odB*U++>HK0t*@*R%(R#z5HR$U??U-@$h~xa!k7KA*V@GjmS*nIGCiN!eVA#aT*lhX|)6oBVa!M6#E z(2|pSWbB6))GRbtH^gO0qw(Ars*=X{u=wWzMeu8RKK_#VX2d05Qnpqu=$tx#0Dj39 z;SDQ7`*KB|OEGc8(PEQ_-L~hTYqHRM?d*PKeqb*pRhry2RD1UAeV;JNgqkbly#`rK z9-u=60uMm-1b6P-(ig7FM?-#Rj z>&rkPfy*Ql!Kod;>sb3JNQQDkha_J0f`yUXmEVMK8(sqcW*d%_uO)K_%A|5EI9F^y z@;?#>5NhD58+1Uen-y&U*L<@446ZK@#3f=`$Ybc{3QUcOd2PT_xI6YOEIok$O@Q($ z0qZD;!C_53vQ*1`T~u<*jT!Sbwi4dEmz*T`4i<`KZI=Osr=1}WJU7c%Wa324zk@gA zUBw(d)oRie1+z8AIk2y*+6N~iOdeIL5FZ~OfvD5?V~`GcPcT8<#Y5A_5dY8`zSHWx zntwZGNKn*wr< zomw7B3pr7_h<82&qx;CS^K^&R&tAv2LwWj|`Wlj~>c}sm0~@iK190O1v8Mb1Q1r+L z{{0o77wl&uJZTz0Z7qe|?vLSpFzODiD8fJJNeCPcXjNUPJ^P;Ok};h{DkJa0Fyjq$&?l<0E)TVCb+uh^ImMP zE*Qf1$e#duUZE;;FN}}eS$TW#)%*jBN$9oJrqKl(34nx6b^(IWmO66mZqaX%$M0j+ zkD)U{@(nQpp+mM!4tO2}E;J{4Zf(goHPihew#}kOmmn`8`CjV}KsrY$6{i6AvOuL? zG_7B#m=m&%R@&5+tX{&zQDNi?1R+Ss|7GmzT%EsgHNy4+)k%!*npG@iohKDPMz~2F zR>SjRY$FA$99*v04haA0U=$#S#9&64XC;6yai4#*p4WB1PZ|b2_p{7#Oy7>$Xpv<*9a4Azj5!_|jsU|R3(dvUjV9J! zpiu3)F$>)!(iGc6CszwtZo0eA8HXxP#H3kyf@W^sc6kE3K1i%yBG1SRG z)fO~<^Y&S555$v8z`#CB-hp76(P@Kg&@p*zhF1PI;7)BPfEoOxrAPh$+$!3Yt3`4p<-HB&CMD$x3kW7EUZhz@C zJ|>~6po1>=gWmVkl04(NB~!|I53#x@i*CbqpU!LU6SziQ|==wM{!IAl1W)_bamZu%+Cbg|Ub z<4dTkA`jj01mY6P`!c?Hb4d^u>dj$*)I(vFd~$yhY&?0pv($G8ecuIELO`5})kU#2 zZ9|x!Q37d_KEkYOG-P~T!f}Z8A0-+@Sw2ow-P6J4w_X*x3$iwf`bn{6CTj3{*@<6~ ztapz&HXL=U#2GQp^Vpj*3S9gQ^hE6BnD(A$r@vZ>d zYQ5{TM>3#>jK|pHUoNgi)0f8r2xeX+aTp2(*U^S}bRNM=wALaU=RTSHmW>AH%8+=@ zFw27^B?F#*F<$}weO#>J<%-@z5x%Dd*z2hSVZq-x07+g5xig8k`zrI+NSi!ZZ9LeK;m4a~{?tDO5{ee@i=#ILdKK62N*3737A zM+xA>#BmQNa2gpNLSup&5~Byc+?N=J1JK7-=VppVolK7>a1<$r*-m`|<>%)x4tjhn`cURGw2*6)HVNzVpcWHt4}11;4)KSU z-aKf`UDdSKP$A!7!Qs%1XP}m}@5uO?EG!<4Xpj4MpDCU1-<5E@D8VAcRHIV7L- zGCs(>a26Q(3GP9+=|qB3{wqhuI)$I{R>SPw{%<1>r>!!z)RtRhr{NTV%77>*XtNHk z4Lb?J@JMnZ#s3;BeAGIMd9& zB8I$e$MWk$f6KN}T2+dYHgiwic-iDqeaxg-dcK%|4LJlyUGq;>1VK1wZGix{h^Cxz zthce+;`N65nd{my*d8Fh;BH0)FNo~pJSz;->($-?4U)X--1_!H)Cl1 z92qqxI5Xh|8#tMG^fsZ%A#Fwnm?|ahc$9asJ8^F6MCWCsxjPUdKZ{=X>MgL}2;rWJ zbphODw(uS?dzAxEdEqXV$w;uk}51)j##>-g{VST-bNPqAQX(x)B}?v)-F7 zc=U=P4=mQTy-A+AmmkqmQWO>aMZ{>~`}UtXrEWGU%e_gr0k|lPY9{_EsKVON<CPt z+F(mMpo^D)xdQ_g5U|X-W|InP?5Tw{!(XHRAoXnYmTlH}>@MVLAi(Cp5#wuc@Jp6iSZm#P4@Hme z`7dTx2QIf%L|VT2S5XUc1;D6A7&*e3^|&V}l>456?;4AlC{X=rnn!_wNm<>;sB%FH zGn7a$DhVwFRb!VSsGl}z@mff0JQP z4($_I_07`@UN%0>zVm6lxQg_wB(-4rN4ulF6a+iD28M$pO99$T+fR!@R^L VqG*mH{|W$qM-LxA^n~J(@IRT#M5X`$ literal 0 HcmV?d00001 diff --git a/icons/save-as.png b/icons/save-as.png new file mode 100644 index 0000000000000000000000000000000000000000..9d2f6082d41c537d8bbfc571234ca6520346c8ad GIT binary patch literal 696 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 F|9>{H{4W3i literal 0 HcmV?d00001 diff --git a/icons/undo.png b/icons/undo.png new file mode 100644 index 0000000000000000000000000000000000000000..40603ed2871a07676de229f1a8098785271e1593 GIT binary patch literal 23352 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 ALI3~& literal 0 HcmV?d00001 diff --git a/install.sh b/install.sh new file mode 100644 index 0000000..7449bd4 --- /dev/null +++ b/install.sh @@ -0,0 +1,38 @@ +#!/bin/sh + +#################################### +# blast installation script +################################### + +if ! [ -f ./blast ]; then + echo "Impossible de trouver l'executable dans le répertoire courant. Arret" + exit 1 +fi +if ! [ -d ./locales ]; then + echo "Impossible de trouver les fichiers de langue dans le répertoire courant. Arret" + exit 1 +fi + +if ! [ -d /usr/local/bin ]; then + mkdir -p /usr/local/bin +fi + +if ! [ -d /usr/local/share/blast ]; then + mkdir -p /usr/local/share/blast +else + rm -rf /usr/local/share/blast + mkdir -p /usr/local/share/blast +fi + +if [ -f /usr/local/bin/blast ]; then + echo "Suppression de la version actuellement installée" + rm -f /usr/local/bin/blast +fi +echo "Installation :" +echo "binaries" +cp blast /usr/local/bin +echo "locales" +cp -r locales /usr/local/share/blast/ +echo "examples" +cp -r examples /usr/local/share/blast/ +echo "done." diff --git a/install.sh.in b/install.sh.in new file mode 100644 index 0000000..040ca55 --- /dev/null +++ b/install.sh.in @@ -0,0 +1,38 @@ +#!/bin/sh + +#################################### +# @@APPNAME@@ installation script +################################### + +if ! [ -f ./@@APPNAME@@ ]; then + echo "Impossible de trouver l'executable dans le répertoire courant. Arret" + exit 1 +fi +if ! [ -d ./locales ]; then + echo "Impossible de trouver les fichiers de langue dans le répertoire courant. Arret" + exit 1 +fi + +if ! [ -d /usr/local/bin ]; then + mkdir -p /usr/local/bin +fi + +if ! [ -d /usr/local/share/@@APPNAME@@ ]; then + mkdir -p /usr/local/share/@@APPNAME@@ +else + rm -rf /usr/local/share/@@APPNAME@@ + mkdir -p /usr/local/share/@@APPNAME@@ +fi + +if [ -f /usr/local/bin/@@APPNAME@@ ]; then + echo "Suppression de la version actuellement installée" + rm -f /usr/local/bin/@@APPNAME@@ +fi +echo "Installation :" +echo "binaries" +cp @@APPNAME@@ /usr/local/bin +echo "locales" +cp -r locales /usr/local/share/@@APPNAME@@/ +echo "examples" +cp -r examples /usr/local/share/@@APPNAME@@/ +echo "done." diff --git a/lib/README.txt b/lib/README.txt new file mode 100644 index 0000000..9ca12b8 --- /dev/null +++ b/lib/README.txt @@ -0,0 +1,119 @@ +------------------------------------ +XML description of a block model : +------------------------------------ + + attributes : + - name : required + - type : required + - value : required if context = constant/generic, optionnal if context = wb, optional but not used if context = user/port + - context : required + - core : required if context = wb, not used for others. Possibles values : r or w. Specify if the parameter will be a register + that is read by the core, i.e. an input port of the core, or written by the core. + +, attributes : + - name : required + - width : required + - purpose : optional. Possible values : clock, reset, wb, data, ... Default value is data + - level : optional. Possible values : basic, top, ... Default value is basic + - multiplicity : optional. Possible values : *, a number. Default value is 1. + + attributes : + - name : required + - width : required + - purpose : optional but forced to data + - level : optional but forced to top + - multiplicity : optional. Possible values : *, a number. Default value is 1. + If an interface has a multiplicity > 1, the user will be able to create several + instances of that interface. Their default name will be the reference name + followed by _X, where X = 1, ..., N, and N the number of instances. + + +-------------------------------------------- +XML description of an implementation model : +-------------------------------------------- + + attributes: + - ref_name: the name of an XML file that contains the block model associated to this implementation. Normally, it should be unique over all available blocks. + - ref_id : the md5 hashsum of the xml file given in ref_name. It is used to quickly retrieve the block model in the applciation. + + block: + - all elements and their attributes are amndatory but they can be void. + + block: + - used to generate the library and use instructions. + - in case of using a standard package (from work or std), only use clauses will be generated. + + block: + - constains only the architecture part of the implementation + - the name of the architecture will be determined automatically from the number of existing implementations for the block model. The name will be the name of the block followed by _X, where X is the rank of the implementation found when directories are parsed to found implementations. + +syntax: + + - @{input/output/bidir} is replaced by the "real" name in the pgrah + of blocks. It is because the reference name given in the reference + block may be changed by the user for a specific instance of that + block in the graph. Using @{name} will lead to do produce a VHDL + code with the name given by the user and not the reference name. + + - @{param} is replaced by the name of the parameter as given in the reference + blokc, which cannot be changed by the user. + + - @val{param} is replaced by the value of the parameter. This value may be + constant, given by user, or sometimes computed according to number of + instances of an interface with multiplicitiy>1. + + - @eval(expression) is replaced by the result of the evaluation of th arithmetic expression. + This expression can use +,-,*,/,(,), numbers and values of params, i.e. @val{param} expressions. + BEWARE : using a generic param value in an @eval{} is inadvisable, since it breaks the principles of + the generic use. + + - @foreach{interface} + // instructions + @endforeach + + only applicable to interfaces with multiplicity>1. Used to produce + a sequence of instruction X times, where X is the number of + interface instances created by the user. For each of those interfaces, + instructions will be generated. If ${interface} is used + within the instructions, it will be replaced by the actual evaluated interface. + + Example : let val_o an output interface with multiplicity="*" + Supposing the user created 3 instances, leaving the two first with their default name (i.e. val_o_1 val_o_2) and the last one with name val_o_last. + Then, if an implementation contains : + @foreach{val_o} + signal @{val_o}_enb : std_logic; + @endforeach + + Then the generated VHDL is : + signal val_o_1_enb : std_logic; + signal val_o_2_enb : std_logic; + signal val_o_last_enb : std_logic; + + - @caseeach{interface,signal,cases} + // instructions + @endcaseeach + + similar to foreach but to produce a case statement. signal is the + signal used to test against the cases. Obviously, there should be + as much cases as the number of interface instances. If not, the + generation will not be complete. + cases may be a predefined variables (see below) or a list of values. + + Example : with the same assumptions as in foreach example, + + @caseeach(val_o,sel_s,@#:1) + @{val_o}_enb <= '1'; + @endcaseeach + + will produce + case sel_s is + when 1 => val_o_1_enb <= '1'; + when 2 => val_o_2_enb <= '1'; + when 3 => val_o_last_enb <= '1'; + end case; + + - @#[-]:number : only usefull within foreach, caseeach, ... Produces an output + that starts at number and increments or decrements (with #-) at each case. + + Example : @#-:3 will produce 3, 2, 1, 0, -1, ... + diff --git a/lib/implementations/apf27-wb-master_impl.xml b/lib/implementations/apf27-wb-master_impl.xml new file mode 100644 index 0000000..7970abf --- /dev/null +++ b/lib/implementations/apf27-wb-master_impl.xml @@ -0,0 +1,69 @@ + + + + + + + + This component is an interface between i.MX signals + and the interconnector component. + + + On i.MX<->FPGA connection : the WEIM part of i.MX has a 16 bits bus address + but only [1:12] bits are connected to FPGA pins. From the i.MX point of view + it means that reading in memory mapped address 0x0002 or 0x0003 gives the same + result since the LSB bit of the address is not transmited. + + These 12 bits are forwarded to the interconnector which is responsible to + determine for what IP the data and addr signals must be routed. + + + + + + + + + + + + + + signal write : std_logic; + signal read : std_logic; + signal strobe : std_logic; + signal writedata : std_logic_vector(@{wb_data_width}-1 downto 0); + signal address : std_logic_vector(@{wb_addr_width}-1 downto 0); + +begin + +-- ---------------------------------------------------------------------------- +-- External signals synchronization process +-- ---------------------------------------------------------------------------- + process(@{clk}, @{reset}) + begin + if(@{reset}='1') then + write <= '0'; + read <= '0'; + strobe <= '0'; + writedata <= (others => '0'); + address <= (others => '0'); + elsif(rising_edge(@{clk})) then + strobe <= not (@{imx_cs_n}) and not(@{imx_oe_n} and @{imx_eb3_n}); + write <= not (@{imx_cs_n} or @{imx_eb3_n}); + read <= not (@{imx_cs_n} or @{imx_oe_n}); + address <= @{imx_addr}; + writedata <= @{imx_data}; + end if; + end process; + + @{addr_o} <= address when (strobe = '1') else (others => '0'); + @{dat_o} <= writedata when (write = '1') else (others => '0'); + @{stb_o} <= strobe; + @{we_o} <= write; + @{cyc_o} <= strobe; + + @{imx_data} <= @{dat_i} when(read = '1' ) else (others => 'Z'); + + + diff --git a/lib/implementations/demux_impl.xml b/lib/implementations/demux_impl.xml new file mode 100644 index 0000000..bcb7ea8 --- /dev/null +++ b/lib/implementations/demux_impl.xml @@ -0,0 +1,71 @@ + + + + + + + + This component is a synchronous demultiplixer with variable number of outputs + + + No notes + + + + + + + + + + + + + signal sel_s : unsigned(@eval{@val{sel_width}-1} downto 0); + signal val_i_dly : std_logic_vector(@eval{@val{val_width}-1} downto 0}; + + @foreach{val_o} + signal @{val_o}_enb : std_logic; + @endforeach + + begin + + sel_s <= @{sel_i}; + + delay_input : process(@{clk}, @{rst}) + begin + if(@{rst}='1') then + val_i_dly <= (others => '0'); + elsif(rising_edge(@{clk})) then + val_i_dly <= @{val_i}; + end if; + end process delay_input; + + + demux : process(@{clk}, @{rst}) + begin + if(@{rst}='1') then + + @foreach{val_o} + @{val_o}_enb <= '0'; + @endforeach + + elsif(rising_edge(@{clk})) then + + @foreach{val_o} + @{val_o}_enb <= '0'; + @endforeach + + @caseeach{val_o,sel_s,@#:1} + @{val_o}_enb <= '1'; + @endcaseeach + + end if; + end process demux; + + @foreach{val_o} + @{val_o} <= val_i_dly when (@{val_o}_enb = '1') else (others => '0'); + @endforeach + + + diff --git a/lib/implementations/impls.bmf b/lib/implementations/impls.bmf new file mode 100644 index 0000000000000000000000000000000000000000..b544d25e4ebde346bafd0ec44ee2a3ffd1e6216f GIT binary patch literal 8 NcmZQzU|?YY0ssIU00jU5 literal 0 HcmV?d00001 diff --git a/lib/implementations/multadd.vhd b/lib/implementations/multadd.vhd new file mode 100644 index 0000000..394b223 --- /dev/null +++ b/lib/implementations/multadd.vhd @@ -0,0 +1,76 @@ +------------------------------------------------------------------------------- +-- +-- File : multadd.vhd +-- Related files : +-- +-- Author(s) : stephane Domas (sdomas@univ-fcomte.fr) +-- +-- Creation Date : 2015/04/27 +-- +-- Description : This component is a multadd +-- +-- Note : No notes +-- +------------------------------------------------------------------------------- + +library IEEE; +use IEEE.std_logic_1164.all; +use IEEE.numeric_std.all; + +entity multadd is + generic ( + wb_data_width : integer := 16; + wb_addr_width : integer := 12 + ); + port ( + -- clk/rst from multadd wrapper + rst : in std_logic; + clk : in std_logic; + + -- registers r/w via wishbone + wb_do_op : in std_logic; + wb_c : in std_logic_vector(wb_data_width-1 downto 0); + wb_d : out std_logic_vector(2*wb_data_width-1 downto 0); + + -- data ports + val1_i : in std_logic_vector(17 downto 0); + val2_i : in std_logic_vector(17 downto 0); + res_o : out std_logic_vector(35 downto 0) + ); +end multadd; + + +architecture multadd_1 of multadd is + + -- Signals + signal a_s : signed(17 downto 0); + signal b_s : signed(17 downto 0); + signal c_s : signed(35 downto 0); + signal result : signed(35 downto 0); + +begin + + a_s <= signed(val1_i); + b_s <= signed(val2_i); + c_s <= resize(signed(wb_c), 36); + + do_mult_process : process (clk, rst) + begin + if rst = '1' then + + result <= to_signed(0, 36); + + elsif (rising_edge(clk)) then + + if wb_do_op = '1' then + result <= a_s * b_s + c_s; + end if; + + end if; + end process do_mult_process; + + res_o <= std_logic_vector(result); + wb_d <= std_logic_vector(resize(result, 2*wb_data_width)); + +end multadd_1; + diff --git a/lib/implementations/multadd_core.vhd b/lib/implementations/multadd_core.vhd new file mode 100644 index 0000000..0f42489 --- /dev/null +++ b/lib/implementations/multadd_core.vhd @@ -0,0 +1,76 @@ +------------------------------------------------------------------------------- +-- +-- File : multadd_core.vhd +-- Related files : +-- +-- Author(s) : stephane Domas (sdomas@univ-fcomte.fr) +-- +-- Creation Date : 2015/04/27 +-- +-- Description : This component is a multadd +-- +-- Note : No notes +-- +------------------------------------------------------------------------------- + +library IEEE; +use IEEE.std_logic_1164.all; +use IEEE.numeric_std.all; + +entity multadd_core is + generic ( + wb_data_width : integer := 16; + wb_addr_width : integer := 12 + ); + port ( + -- clk/rst from multadd wrapper + rst : in std_logic; + clk : in std_logic; + + -- registers r/w via wishbone + wb_do_op : in std_logic; + wb_c : in std_logic_vector(wb_data_width-1 downto 0); + wb_d : out std_logic_vector(2*wb_data_width-1 downto 0); + + -- data ports + val1_i : in std_logic_vector(17 downto 0); + val2_i : in std_logic_vector(17 downto 0); + res_o : out std_logic_vector(35 downto 0) + ); +end multadd_core; + + +architecture multadd_core_1 of multadd_core is + + -- Signals + signal a_s : signed(17 downto 0); + signal b_s : signed(17 downto 0); + signal c_s : signed(35 downto 0); + signal result : signed(35 downto 0); + +begin + + a_s <= signed(val1_i); + b_s <= signed(val2_i); + c_s <= resize(signed(wb_c),36); + + do_mult_process : process (clk, rst) + begin + if rst = '1' then + + result <= to_signed(0,36); + + elsif (rising_edge(clk)) then + + if wb_do_op = '1' then + result <= a_s * b_s + c_s; + end if; + + end if; + end process do_mult_process; + + res_o <= std_logic_vector(result); + wb_d <= std_logic_vector(resize(result,32); + +end multadd_core_1; + diff --git a/lib/implementations/multadd_ctrl.vhd b/lib/implementations/multadd_ctrl.vhd new file mode 100644 index 0000000..b02bd3d --- /dev/null +++ b/lib/implementations/multadd_ctrl.vhd @@ -0,0 +1,154 @@ +------------------------------------------------------------------------------- +-- +-- File : multadd_ctrl.vhd +-- Related files : multadd.vhd +-- +-- Author(s) : stephane Domas (sdomas@univ-fcomte.fr) +-- +-- Creation Date : 2015/04/27 +-- +-- Description : This component is generated automatically. +-- It contains processes to r/w registers defined for multadd +-- via wishbone. +-- +-- Note : No notes +-- +------------------------------------------------------------------------------- + +library IEEE; +use IEEE.std_logic_1164.all; +use IEEE.numeric_std.all; + +------------------------------------------------------------------------------- +-- wishbone registers +------------------------------------------------------------------------------- +-- name bits address R/W +-- wb_do_op 0 0x0000 W +-- wb_c (15 downto 0) 0x0001 W +-- wb_d (15 downto 0) 0x0002 R +-- wb_d (31 downto 0) 0x0003 R +-- +------------------------------------------------------------------------------- + +entity multadd_ctrl is + generic ( + wb_data_width : integer := 16; + wb_addr_width : integer := 2 + ); + port ( + -- clk/rst from interconnector + rst : in std_logic; + clk : in std_logic; + + -- addr/data from interconnector + addr_i : in std_logic_vector(wb_addr_width-1 downto 0); + dat_i : in std_logic_vector(wb_data_width-1 downto 0); + dat_o : out std_logic_vector(wb_data_width-1 downto 0); + cyc_i : in std_logic; + stb_i : in std_logic; + we_i : in std_logic; + ack_o : out std_logic; + + -- registers r/w via wishbone that are forwarded to the block + wb_do_op : out std_logic; + wb_c : out std_logic_vector(wb_data_width-1 downto 0); + wb_d : in std_logic_vector(2*wb_data_width-1 downto 0) + + ); +end multadd_ctrl; + + +architecture multadd_ctrl1 of multadd_ctrl is + + -- signals : registers r/w via wishbone + signal wb_do_op_s : std_logic; + signal wb_c_s : std_logic_vector(wb_data_width-1 downto 0); + signal wb_d_s : std_logic_vector(2*wb_data_width-1 downto 0); + + -- signals : wishbone related + signal read_data : std_logic_vector(wb_data_width-1 downto 0); + signal read_ack : std_logic; + signal write_ack : std_logic; + signal write_rise : std_logic; + +begin +-- ---------------------------------------------------------------------------- +-- signals from/to ports +-- ---------------------------------------------------------------------------- + wb_d_s <= wb_d; + wb_c <= wb_c_s; + wb_do_op <= wb_do_op_s; + +-- ---------------------------------------------------------------------------- +-- write rising edge detection +-- ---------------------------------------------------------------------------- + detection_front : process(gls_clk, gls_reset) + variable signal_old : std_logic; + begin + if (gls_reset = '1') then + signal_old := '0'; + write_rise <= '0'; + elsif rising_edge(gls_clk) then + if (signal_old = '0' and stb_i = '1' and cyc_i = '1' and we_i = '1') then + write_rise <= '1'; + else + write_rise <= '0'; + end if; + signal_old := we_i; + end if; + end process detection_front; + +-- ---------------------------------------------------------------------------- +-- Register reading process +-- ---------------------------------------------------------------------------- + reading_reg : process(clk, rst) + begin + if(rst = '1') then + read_ack <= '0'; + read_data <= (others => '0'); + elsif(rising_edge(clk)) then + read_ack <= '0'; + if (stb_i = '1' and cyc_i = '1' and we_i = '0') then + read_ack <= '1'; + if(addr_i = "10") then + read_data <= wb_d_s(15 downto 0); + elsif(addr_i = "11") then + read_data <= wb_d_s(31 downto 16); + else + read_data <= (others => '0'); + end if; + end if; + end if; + end process reading_reg; + +-- ---------------------------------------------------------------------------- +-- Register writing process +-- ---------------------------------------------------------------------------- + writing_reg : process(clk, rst) + begin + if(rst = '1') then + write_ack <= '0'; + wb_do_op_s <= '0'; + wb_c_s <= (others => '0'); + elsif(rising_edge(clk)) then + write_ack <= '0'; + wb_do_op_s <= '0'; + if (write_rise = '1') then + write_ack <= '1'; + if (addr_i = "00") then + wb_do_op_s <= '1'; + elsif (addr_i = "01") then + wb_c_s <= dat_i; + end if; + end if; + end if; + end process writing_reg; + +-- ---------------------------------------------------------------------------- +-- assignations for wishbone outputs +-- ---------------------------------------------------------------------------- + ack_o <= read_ack or write_ack; + dat_o <= read_data when (stb_i = '1' and cyc_i = '1' and we_i = '0') else (others => '0'); + +end multadd_ctrl1; + diff --git a/lib/implementations/multadd_impl.xml b/lib/implementations/multadd_impl.xml new file mode 100644 index 0000000..fc2d10f --- /dev/null +++ b/lib/implementations/multadd_impl.xml @@ -0,0 +1,63 @@ + + + + + + + + This component is a multadd + + + No notes + + + + + + + + + + + + + -- Signals + signal a_s : signed(@eval{@val{in_width}-1} downto 0); + signal b_s : signed(@eval{@val{in_width}-1} downto 0); + signal c_s : signed(@eval{2*@val{in_width}-1} downto 0); + signal result : signed(@eval{2*@val{in_width}-1} downto 0); + +begin + + a_s <= signed(@{a}); + b_s <= signed(@{b}); + c_s <= resize(signed(@{wb_c}),@eval{2*@val{in_width}}); + + do_mult_process : process (@{clk}, @{rst}) + begin + if @{rst} = '1' then + + result <= to_signed(0,@eval{2*@val{in_width}); + + elsif (rising_edge(@{clk})) then + + if @{wb_do_op} = '1' then + result <= a_s * b_s + c_s; + end if; + + end if; + end process do_mult_process; + + @{d} <= std_logic_vector(result); + @{wb_d} <= std_logic_vector(resize(result,2*wb_data_width)); + + + + + + + + + + + diff --git a/lib/references/apf27-wb-master.xml b/lib/references/apf27-wb-master.xml new file mode 100644 index 0000000..82ee08a --- /dev/null +++ b/lib/references/apf27-wb-master.xml @@ -0,0 +1,46 @@ + + + + + wishbone master for apf27 + + + + + This block is the wishbone master of the design, connected to the i.MX of APF27 + + + This block is the wishbone master of the design, connected to the i.MX of APF27 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/lib/references/demux.xml b/lib/references/demux.xml new file mode 100644 index 0000000..cd66442 --- /dev/null +++ b/lib/references/demux.xml @@ -0,0 +1,36 @@ + + + + + block demultiplexer + + + + + This block demux an entry presented on FPGA pins over a variable number of outputs + + + This block demux an entry presented on FPGA pins over a variable number of outputs + + + + + + + + + + + + + + + + + + + + + + + diff --git a/lib/references/multadd.xml b/lib/references/multadd.xml new file mode 100644 index 0000000..7ef16bd --- /dev/null +++ b/lib/references/multadd.xml @@ -0,0 +1,43 @@ + + + + + block multiply/add + + + + + This block multiplies 2 input values, adding the result to a third one. + + + This block does d=a*b+c. + a/b are provided by input port. + c is set via the wishbone interconnector + d is forwarded to an output port and can be retrieved via the wishbone interconnector + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/lib/references/references.bmf b/lib/references/references.bmf new file mode 100644 index 0000000000000000000000000000000000000000..6685627c4574b4e5f554b3157b7bef51a2406f6d GIT binary patch literal 5658 zcmd^@(QX@65QZmi8igoSD2j5$g@qz63Qg^GQrj0uEf*jpC=udw#;nBtdw!xw6+aw zXsP{R53FimaD0utV-M{a@+0fg8gs99i`GNiw-I;W^W=oPPqCL=?G$+@SiG=`?O1|r zk^db##oK^>zO*M+#6y|g~hpCPSsNaNU^Bl&nD8HPKT-MqgNF)nc| zb37)>4te#g5zf1uACYs{)Jm;RtHxhi36ecz4UQgK75>S@*D4W@(He2TiEfK*T6PdJ z?Sx#-*28#=LcWLSmGESChhqhg17=IIYB+m@ic-f4el;8MxoqMyUDpde_PO7(?Se$3 z9b+h3#dgtpc&T+9Jz4WB{mE$-aS4b^ZV_?Oe~!5QD zk+%z)s^F{&j_Lt*sWm~u9_Xsj>ak`Lu(!+83Nx4>NkP>fxX}~Gz?&maQCt^!_og_j zg7&LnFSkvuY^#yX?bY|ykel18@2lC;b?J9bXHoz3mQgSNH;4MC3sC>fHCIyE7I~e; zeFHo<1FGvFxfv>HLhWp_5>jy8pq?aE& zl%gXRAl+fXqR)!_%DzM5o}RFCd^Q=ee}9CtD^shlO^#ZNVnAAjHSX(3+hIM{!+dEs z>hWAUZCZ6SwJZ5=mCJSamG_u~BN*iYXz1`4uX-O=BRu4?S$?&Ij*gg9?jvPR517Ts zpU&4DlG8<+8`HSDL{hs@ca7M^!F^wJW~V(j_O9z1xrr;fPM+an203qi7Z2;x5%PU3 z_Yp_#>?p+%nvFoaU~auQ0$I#jr}m00y>HL)td6*==1n~;)>&m{HS*_c$ZI~MF}l}_ zSbTXB^_@Rxe@Ha(4XMlwbivwlBqz-7G1o50b1r}SmTF#%;k2;7|8`D&%XL+sGbX|VvN8GuN zSGakm{9lPP;?~)>SVkkTjC#md+`7Vji&dS%HEL8f@sSqNB;1R8h_{HX?@ka^WZxTM z{05-7_{Meit*?UZD)XcrR_}BhVYcS&HW>LVV6zuyOEam}zRg{aJ~EwN2=`Z$B%xZ~eI^vkFO>>A=Lg0K((W-dO`%?xN^%wfenate}(BBS7^=}>G zFvV5;DsTzy9%etirF9%5@Y>v%MWA@5gf@(pMpmqGO-X(|tzxBi6x=y$jmjL89+Jib4H( zy-^j>HwLdZ?NEn`@kgvjA1QXu(u9%|?qnZhUCi3#&l%t>C(|nO$dsRZb@uu)#2BM; z*5>uHSizKmuSBn=@}gC*imAF+4tc7&YIamDrE$G@eccGp+QAhyfS%D8xxLe8(JE4vaiuCIGOwu}7>Rk08I2ZY-XqyPW_ literal 0 HcmV?d00001 diff --git a/object-files.txt b/object-files.txt new file mode 100644 index 0000000..841a369 --- /dev/null +++ b/object-files.txt @@ -0,0 +1,47 @@ +COMMON-OBJ = $(BUILDPATH)/AbstractBlock.o \ + $(BUILDPATH)/FunctionalBlock.o \ + $(BUILDPATH)/ReferenceBlock.o \ + $(BUILDPATH)/GroupBlock.o \ + $(BUILDPATH)/AbstractInterface.o \ + $(BUILDPATH)/ConnectedInterface.o \ + $(BUILDPATH)/FunctionalInterface.o \ + $(BUILDPATH)/GroupInterface.o \ + $(BUILDPATH)/ReferenceInterface.o \ + $(BUILDPATH)/BlockImplementation.o \ + $(BUILDPATH)/Graph.o \ + $(BUILDPATH)/AbstractBoxItem.o \ + $(BUILDPATH)/BoxItem.o \ + $(BUILDPATH)/GroupItem.o \ + $(BUILDPATH)/BlockCategory.o \ + $(BUILDPATH)/BlockLibraryTree.o \ + $(BUILDPATH)/BlockLibraryWidget.o \ + $(BUILDPATH)/moc_BlockLibraryWidget.o \ + $(BUILDPATH)/BlockParameter.o \ + $(BUILDPATH)/BlockParameterUser.o \ + $(BUILDPATH)/BlockParameterGeneric.o \ + $(BUILDPATH)/BlockParameterPort.o \ + $(BUILDPATH)/BlockParameterWishbone.o \ + $(BUILDPATH)/ConnectionItem.o \ + $(BUILDPATH)/Dispatcher.o \ + $(BUILDPATH)/Exception.o \ + $(BUILDPATH)/Graph.o \ + $(BUILDPATH)/GroupScene.o \ + $(BUILDPATH)/InterfaceItem.o \ + $(BUILDPATH)/MainWindow.o \ + $(BUILDPATH)/moc_MainWindow.o \ + $(BUILDPATH)/ArithmeticEvaluator.o \ + $(BUILDPATH)/moc_ArithmeticEvaluator.o \ + $(BUILDPATH)/BlockWidget.o \ + $(BUILDPATH)/moc_BlockWidget.o \ + $(BUILDPATH)/GroupWidget.o \ + $(BUILDPATH)/moc_GroupWidget.o \ + $(BUILDPATH)/Parameters.o \ + $(BUILDPATH)/moc_Parameters.o \ + $(BUILDPATH)/rcc_blast.o \ + $(BUILDPATH)/BlocksToConfigureWidget.o \ + $(BUILDPATH)/moc_BlocksToConfigureWidget.o \ + $(BUILDPATH)/ParametersWindow.o \ + $(BUILDPATH)/moc_ParametersWindow.o \ + $(BUILDPATH)/InterfacePropertiesWindow.o \ + $(BUILDPATH)/moc_InterfacePropertiesWindow.o \ + $(BUILDPATH)/blast.o diff --git a/projectfile.xsd b/projectfile.xsd new file mode 100644 index 0000000..36fe54e --- /dev/null +++ b/projectfile.xsd @@ -0,0 +1,333 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/save/sauv.xml b/save/sauv.xml new file mode 100644 index 0000000..2493342 --- /dev/null +++ b/save/sauv.xml @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/save/test.xml b/save/test.xml new file mode 100644 index 0000000..ac40f85 --- /dev/null +++ b/save/test.xml @@ -0,0 +1,69 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/testproject.xml b/testproject.xml new file mode 100644 index 0000000..f482cfd --- /dev/null +++ b/testproject.xml @@ -0,0 +1,84 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + -- 2.39.5

TmuZ%ySv}3ji;RzU$o)$V|3z_T$ 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 ML_r~@Add9k0R0*lcK`qY literal 0 HcmV?d00001 diff --git a/doc/models-tg.tex b/doc/models-tg.tex new file mode 100644 index 0000000..fd39c6f --- /dev/null +++ b/doc/models-tg.tex @@ -0,0 +1,376 @@ +\documentclass[12pt,fleqn,a4paper]{article} +\usepackage[latin1]{inputenc} +%\usepackage[cyr]{aeguill} +\usepackage{xspace} +\usepackage{amsmath} +\usepackage{amsfonts} +\usepackage[english]{babel} +\usepackage{url} +\usepackage{multirow} +\let\urlorig\url +\renewcommand{\url}[1]{% + \begin{otherlanguage}{english}\urlorig{#1}\end{otherlanguage}% +} + +%\usepackage{pstricks,pst-node,pst-text,pst-3d} +\usepackage{graphicx} +\usepackage{thumbpdf} +\usepackage{color} +\usepackage{moreverb} +%\usepackage{commath} +\usepackage{subfigure} +%\input{psfig.sty} +\usepackage{fullpage} +\usepackage{fancybox} + +\usepackage[ruled,lined,linesnumbered]{algorithm2e} + +%%%%%%%%%%%%%%%%%%%%%%%%%%%% LyX specific LaTeX commands. +\newcommand{\noun}[1]{\textsc{#1}} + +%%%%%%%%%%%%%%%%%%%%%%%%%%%% my own LaTeX commands. +%\newcommand{\bibpath}[1] +% {/home/sdomas/rapport/eigenjace/ipdps/#1} + +\newcommand{\tab}{\ \ \ } +\newcommand{\ot}[1]{{\tt #1}} +%%%%%%%%%%%%%%%%%%%%%%%%%%%% my bib path. + + +\title{BLAST: classes and xml files for blocks (models, implementations and view)} + +\author{St\'ephane Domas\\ +Laboratoire d'Informatique +de l'Universit\'e de Franche-Comt\'e, \\ +BP 527, \\ +90016~Belfort CEDEX, France\\} + + + +\begin{document} + +\maketitle + +\thispagestyle{empty} +\section{What are blocks and items ?} +A block is the main unit to build a design in Blast. From the user +point of view, a block represents a process with inputs to consume +data, outputs to produce results. It may have some parameters +(e.g. the input/output data size). Thus, the user is not concerned +about how the block is really implemented in VHDL, just about the +"function" of the block. Some blocks are predefined, but the user can +also build them by composing predefined ones. + +For the first type, the user has to choose the predefined blocks from +a library of "reference blocks" that do a particular processing +(i.e. a multiplication, a filter, ...), with default interface names +and parameter values. To add such a block to the design, a reference +block is cloned to obtain a "functional" block. It is similar to the +instanciation in object oriented programming: reference block +corresponds to the class, and functional block to the instance of that +class. Thus, a design may contain several functional blocks that +correspond to the same reference block. A reference block is +associated to one or several implementations that are in fact patterns +of VHDL code. One of them will be chosen more or less automatically by +Blast to produce the final VHDL code. The representation of an +implementation as a class and the structure of the files that contain +patterns are discussed in details in Section \ref{Implementations}. + +The second type is named a "group" block. It is created empty or with +a set of existing functional blocks. For example, when a void design +is created, a group block is also created, that will represent the top +component in the VHDL code. User can add predefined blocks or even +other group blocks within a group very easily. There main purpose is +to make the design clearer and the VHDL more readable. Indeed, each +group will be translated into a VHDL component. Thus, instead of +having a single top component with a bunch of predefined blocks +within, they can be spread within groups to build a design +hierachically. + +These three types of block are represented by a hierarchy of classes +that is described in details in Section \ref{sec:blocks_class}.\\ + +Reference, functional and group blocks are just containers of data, +i.e. they are part of the Model in MVC paradigm). The two latter have +a graphical counter part, named items, so that they can be represented +in the graphical interface. The hierarchy of classes +that represents the items is described in details in Section \ref{sec:items_class}.\\ + +\section{Class hierarchy for blocks} +\label{sec:blocks_class} + +\subsection{the top class} +\label{sec:abstract_block_class} + +The top class for describing block is {\tt AbstractBlock}, derived in +three subclasses: {\tt ReferenceBlock}, {\tt FunctionalBlock}, {\tt + GroupBlock}. + +Some attributes are common to every types, and defined in {\tt AbstractBlock}: +\begin{itemize} +\item a name, +\item parameters, +\item input/output/bidirectional interfaces. +\end{itemize} + +Parameters and interfaces are complex to represent and thus, are also +described by a hierarchy of classes in Section +\ref{sec:parameters_class} and \ref{sec:interfaces_class}. + +\subsection{reference blocks} +\label{sec:reference_block_class} + +A reference block is created taking informations in an XML file that +describes it, providing its parameters and interfaces. The structure +of that file is described in details in Section +\ref{sec:references_xml}. + +A reference block also contains a list of {\tt + BlockImplementation}. Each of them represents a pattern of VHDL code +that implements the function of the block. + +\subsection{functional blocks} +\label{sec:functional_block_class} + +A functional block has two attributes {\tt reference} and {\tt group} +that respectively refer to the reference block that has been cloned to +create that functional block, and the group block that contains it. + + +\subsection{group blocks} +\label{sec:group_block_class} + +A group block has mainly two attributes {\tt parent} and {\tt + blocks}. The first one is a pointer to the group that contains that +group (or {\tt NULL} if it is the top group). The second one is the +list of the functional or group blocks that are within this group. + +{\bf Important: } a group block has no interface by itself. In fact, +they are created when the user wants to connect an interface of a +inner block to the outside of the block. Note that there is a 1:1 +relation between a group interface and an interface of an inner +block. Nevertheless, an output group interface can still be connected +to several input interfaces. + +{\bf Important: } a group block has no parameters by itself, except +if it contains blocks that may be configured via a wishbone. In this +case, the generic parameters to manage the wishbone are atuomaticcaly +added to the group. + +\subsection{interfaces classes} +\label{sec:interfaces_class} + +As for blocks, a top class {\tt AbstractInterface} represents the +common features of all interfaces and it is derived in three +subclasses: {\tt ReferenceInterface}, {\tt FunctionalInterface} and +{\tt GroupInterface}. + +\subsection{parameters classes} +\label{sec:parameters_class} + + +There are four types of parameters for a block: +\begin{itemize} +\item user, +\item generic, +\item port, +\item wishbone. +\end{itemize} + +These are represented by the top class {\tt BlockParameter} and four +subclasses: ({\tt BlockParameterUSer}, {\tt BlockParameterGeneric}, +{\tt BlockParameterPort}, {\tt BlockParameterWishbone}. + +Four attributes are common to all, and defined in {\tt BlockParameter}: +\begin{itemize} +\item \ot{owner}: a pointer to the block instance (reference/functional) that ``owns'' this parameter, +\item \ot{name}: the name of the parameter (NB: cannot be changed by the user of BLAST), +\item \ot{type}: the type of the value, +\item \ot{value}: the default value of the parameter, given as a \ot{QString} but stored as a \ot{QVariant}. +\end{itemize} + +\ot{type} value (int) must be set with one of those in \ot{ParamType} enum (cf. \ot{BlockParameter.h}): + +\begin{verbatim} +enum ParamType { Undefined = -1, Expression = 1, Character, + String, Bit, BitVector, Boolean, + Integer, Natural, Positive, Real, Time}; +\end{verbatim} +Except the two first, these values correspond to predefined types in +VHDL. \ot{Expression} correspond to an arithmetic expression and must +be used only for port and wishbone parameters. Thus, its syntax will +be presented in the associated sections (\ref{sec:parameters_port} and +\ref{parameters_wb}).\\ + +Whatever the case, parameters will be used during VHDL generation but at different phases, depending on the class. + +\subsubsection{user parameters} +\label{sec:parameters_user} +User parameters have a type equals to \ot{String}. A default value +must be given at construction but a \ot{userValue} can be defined via +setters.\\ + +A user parameter is only used when generating the VHDL code of the +architecture section of a block (cf. section +\ref{sec:impls_xml}). Each time the generator encounters an escape +sequence: \ot{@val\{user\_parameter\_name\}}, it replaces it with +\ot{userValue} if defined, or with the default value.\\ + +{\bf CAUTION:} No validity check are done by BLAST on default and user +value strings. Thus, they can be anything and can lead to incorrect +VHDL. + +\subsubsection{generic parameters} +\label{sec:parameters_generic} + +Generic parameters have a type equals to any predefined VHDL type (i.e. all +defined above except \ot{Undefined} and \ot{Expression}). A default +value must be given at construction but a \ot{userValue} can be +defined via setters.\\ + +A generic parameter is used during the generation of: +\begin{itemize} +\item the entity section, +\item the component section when the owner block is used within another block, +\item the generic map of when instanciating owner block is used within another block, +\item the architecture section. +\end{itemize} + +In the two first cases, it leads to lines like + +\ot{d\_width : integer := 16;}, using the \ot{name}, \ot{type} and default value. + +In the third case, it leads to lines like + +\ot{d\_width => 10,}, using the \ot{name} and \ot{userValue}, +or default value if not defined. + +\ot{d\_width => d\_width,}, using only the +\ot{name}. This case occurs when the owner is instanciated within a +block that has a generic parameter with the same name. + +In the last case, each time the generator encounters an escape +sequence: \ot{@val\{generic\_parameter\_name\}}, it replaces it with +\ot{userValue} if defined, or with the default value.\\ + +{\bf IMPORTANT:} a block that defines wishbone parameters will be +generated with 2 generic parameters with predefined names: +\ot{wb\_data\_width} and \ot{wb\_addr\_width}. They correspond to the +width of the address and data buses of the wishbone and are used +during the generation of the controller of the block +(cf. \ref{sec:parameters_wb}). + +\subsubsection{port parameters} +\label{sec:parameters_port} +A port parameter can be used to obtain a value associated to an +interface of the block. Indeed, it is possible to create several +instances of the same interface, when its multiplicity given in the +reference model is greater than 1. For example, it is used for block +that may have a variable number of inputs/outputs, like a +multiplexer. Instead fo creating a bunch of multiplexers with 2, 3, 4, +$\ldots$ data inputs, it is possible to generate one for any +amount. In BLAST, this amount is given by the number of instances of +the data input the user has created. It implies that the selector +input has a variable range and thus needs a variable number of bits to +be expressed. If $N$ is the number of instances of the data input, +then the selector size is $log_2(N)$. A port parameter is used to +express such a value.\\ + +A port parameters have a supplementary attribute \ot{ifaceName} that +must correspond to an existing interface of the block. \ot{type} is +equal to \ot{Expression} and \ot{value} contains this expression, +using variables with a predefined name \ot{\$if\_nb} and +\ot{\$if\_width} that will be respectively replace during VHDL +generation by the number of instances of \ot{ifaceName}, and its +width. + +A port parameter is used during the generation of: +\begin{itemize} +\item the entity section, +\item the component section when the owner block is used within another block, +\item the architecture section. +\end{itemize} + +In every case, each time the generator encounters an escape sequence: +\ot{@val\{port\_parameter\_name\}}, it replaces it with the computed +value of the parameter using the expression and the interface name. + + +\subsubsection{wishbone parameters} +\label{sec:parameters_wb} + +A wishbone parameter corresponds to a register that can be read/write +via the wishbone bus. Nevertheless, the GUI allows the user to disable +the wishbone access and replace it by a fixed value or a port that is +assign to the register. + +Since a register has a width, \ot{type} gives its type. Valid types are: +\begin{itemize} +\item \ot{boolean} if the register is an \ot{std\_logic}, +\item \ot{natural} if the register has a fixed width and is a \ot{std\_logic\_vector}, +\item \ot{expression} if the register has a variable width and is a \ot{std\_logic\_vector}, +\end{itemize} +The width is given in a supplementary attribute \ot{width}. Note that: +\begin{itemize} +\item if the type is boolean, width should be equal to 1 but in fact is not used. + +\item if the type is natural and width is equal to 1, it leads to +\ot{std\_logic\_vector(0 downto 0)}, which is sometimes usefull for +memory accesses. + +\item if the type is an expression, width must contains an expression + using only +,-,*, numbers and generic parameter references + (i.e. parameter name prepend with a $). No check is done thus, the + model must be correct so that valid VHDL will be produced. +\end{itemize} + +In the second case, the expression may use predefined names +\ot{wb\_data\_width} and \ot{wb\_addr\_width}. + +Whatever the case, during VHDL generation, the final width will be +compared to the wishbone data bus width. If the bus width is lesser +than the register width, then several wishbone accesses are needed to +read/write the register. For example, if wishbone width is 16 and a +register has a width of 20, we need to define two addresses, one to +access to bits 0 to 15, and one for bits 16 to 19. The total number of +needed addresses gives the minimal width of the address bus (i.e. log2(nb addr)).\\ + +The value is a initialization value. If not provided, it is 0 by +default.\\ + +Three other attributes are declared: +\begin{itemize} +\item \ot{wbAccess}: indicates if the register is written or read by + the wishbone bus. Thus, if it is written, the register is an input + of the block and if it is read, the block provides its value. +\item \ot{wbValue}: it may be a natural, a boolean, or the word + data. In the two first cases, the given value is affected to the + register as soon as the register is accessed in writing. In the + third case, the register is affected with the value that is provided + on the wishbone data bus. +\item \ot{wbDuration} : indicates if the affectation is permanent or + just a trigger. In the second case, the value is set for just one + clock cycle and then reset to the initialization value given by the + \ot{value} attribute.\\ +\end{itemize} + +A wishbone parameter is used during the generation of: +\begin{itemize} +\item the controller of the block (NB: this generation is done at the block level), +\item the entity section, because register values are in fact read/write by the block via input/output ports, +\item the component section when the owner block is used within another block. +\end{itemize} + +\section{Implementations XML description} +\label{sec:impls_xml} + +\section{Implementations class hierarchy} +\label{sec:impls_class} + + +\end{document} + + + + + diff --git a/icons/add_block.png b/icons/add_block.png new file mode 100644 index 0000000000000000000000000000000000000000..6a12252a06a8c9c42280dcae1e9e3c4e84f01a12 GIT binary patch literal 3242 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 + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + bloc + + + diff --git a/icons/add_connection.png b/icons/add_connection.png new file mode 100644 index 0000000000000000000000000000000000000000..cb67913bcbc7a9f23eb36879b8b9d4cbcb51c2d5 GIT binary patch literal 2112 zcmZuydpOe#8~$x(mWZ`-SULT$$Z2>Tl6T(bC-EX_6J@oMb7fz1m_ypuEQ@Fz=ad}k ztvRHVhGLW=iHcIBF)umHF-(%rdwt(O-*tV@b=~)UJd$KZ&lf=IDta0@>pssVpR}yq6Z1JTFI!RX{-Gj+O9x409YFS0~q6`OWf*EJH{;_ zW^Z(8O#I=KAwYb5yk*3RD0=W=T8L%z$*|lBXDk3HJ@tTgkQjNgd`bpspN{zSO7A`F7&TVdt zwG~0t+3u1GuJNaVYO-FyE#cj(_1Wj@ zop-)2VU>&H$ZbI~BTT>NI{05$3aS8hqxMDnK|oiEg>S&4D~{F*30>caKw5JTL+5Fv zRs%*d?TIje`lkd zpH@PSb*C7Yug4S;p}{DGQFx$$y14xGdxcE@t=|$<+#7KHfmN8}?0|_A#j6hllceJg z0q=cb;Jb4RG$rQM38TlyCxhSCys0q)cEuXYpF1Rq78}OSv``o>CQV$PulDjE)1DS9 z^X}oY@;Xz&`M&lz+VUS^_z3-15mSE~K}Yo~sO#uS{i!9Ktdu&#i~8vfaf)iStPnmT zhlQBRNgLPX*A6I;>NoWt$#~_9xfu=j z1Z*N6TNtkJos3p>SUk;@b{mE&;L$32nEzx5;h8@suzd}i%I;}~-)h3TgvY?JJ1oG> zFkKwC%_u{DdnczjNJC$5&p)eqs1-3rqKLVXknPPm4bKAV?O{0u*^nc5pMvvn4glUq z$=CNO7Z<>O5ML#FSnu4W?cc0{PB7sl&xmUo@5k1`lgP1K8AM->lwl|K=@O%o+|~I> ztV8hhq6`du($Sm`^M|LVR;%%v9msRE{x$>+E>a7)BWxGpSZS+4Z0Q@?$M@`j%8*F6 zK^Bn>czm=>QAaQ8PKD#kWx=L3gjgM!LkaP2YtOZ+%k3_xEAQL0iWX^OI&F>e7Sk7* zyZJg6)t&Ff!=$-rpXgo#&}{niQ~RksCF6ynyKa@y)Ns|DnBzL@%(l3Sq2_Tul8m5x@MH`zTfOPkRgenyrJ}wzVUod&Yu? z9|F-(iCVyZ$B95pPmuaB9}%-~&3x))?E_F^abRyrRCO1!tE0MXaH*y#1nsHIHU1o& z(kXRpsk%66HgX@eGfjX(Qb8>Ojtvu_xLG_ZfMfRugIY`;RsLVA&5&LDY%7+yh~IF{ zO>3QQkW=n)(zd*#11`p;jVtXT=yeL0QqSBA-uoag^0F;~G;>PT$&4)wWz}nDw%1)W zaEXw)8fU0^^HXO1@GTT{a-7CNue{cO4QLK~bEELv@}%7#)H+567A`BsRTCI?u<@4% zOf{HQ#-=|e4mPtlmOq?(#3AHSF8UE}8LpuKtQQ5@MA8LH21s2Ub6eV zWqR;W<$%eBM|pLp%m-R&Tc&iV>S^N|jnjO7v6w);BqZDrN>d$jLg`kghvx06x%EnW zTjsaN&QK)j>Pz%eJ?R{4)Qx2zdEVt;9f3u&<$LSCDx0~W7ZU>#$SnrsWy)q&PEiDY z^d*D`Fbz}IXcaMpPxeCm?|uA#i{ojUIrViO4TE zZ3AC@XmQaFthDjsywWm%(wymKyDcwQ)|=C^IUl9#BlR9jSHu+BAkQedZhu#()O<=X z{GDFhP!dm`JMxWH!RGnGgib2X6i1)>1YY5mcZJ?}bLG*Q#aSx0v4tfRxra&izO-4} z`~f1AI5l5qUV-{rW#l%gBa0~)Wp*}>uKb02^S9P^Lerv4*0tTPNp9rHirNk`6(q6J zj=>MYoj{dYLf>x>>F5_CcobGBE(or~G7O8gBoRVHhdnvZNb>4aF^Xy) o1^cjSO4D(lPyJqI + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + diff --git a/icons/add_group.png b/icons/add_group.png new file mode 100644 index 0000000000000000000000000000000000000000..fd3ac9e46040437257078a6234bf741cc8ef4136 GIT binary patch literal 3019 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 + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + group + + + + + + + diff --git a/icons/add_group_select.png b/icons/add_group_select.png new file mode 100644 index 0000000000000000000000000000000000000000..d1d176c73a90b99f7d4df355acd4d6ba331ae182 GIT binary patch literal 3473 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 + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + group + + + + + + + + diff --git a/icons/add_group_small.png b/icons/add_group_small.png new file mode 100644 index 0000000000000000000000000000000000000000..823db6344bc17152dcf9fe68002eaafd469ecf47 GIT binary patch literal 1395 zcmV-(1&sQMP)SK0~Jhfnv`Hch{j?|Ezt+@ zf@>3e@S#De4Dw5V8VL)zF% zi*5JwVYYU5-I?v~&d#n4zhsh``OZ1t{AbSj&TRlR!;USgO0tKKd5U2p_46 zK#^O8S%sZe)lRnxju#P`aI-wa$tWTPKpoHv6acNj5E3=1shovPXdi~(z6 zzG@e+t%|A|6p=%~J3uZFw3z==)v1vEHDC#*xNcy%h zuwPZDfG?~GRP|)Uby#o^k?(*7sv4Fr)d;igq6fI_rSk|@U{?7y;D-eAcdP2V)*I;_ zLb-LcSfiq~DqaMxB=Dx(rWY8BBmJ$w%E+5mVg7WEJQ77jnusE7-VTE2tuWhgFCyD8 zqg1jCsp^L=loJ4zcve-6=^-9IQPme?CGtX;6Q!76^uQd` z*8yd7Ntg~{FNny@^r#YZRNgX|j3QEwIY*31EMfH`GB}rlY^aF1em#k@;=)N-GiFsO zP148FnG;og#%d6ex05W}7N^CWt*LVA1y?)h{~MkPix`0@=0-?LNJqF|#q65KE2(ng zG|_CsoJ%E3kE+&Nu7@lmk#V3wMDBAcAg92{zGk_RyKrUR;)#osfw?$!`eJtgyMPOG zDaeL9F*hpgcneE+5RFxP3%BH#Wj}z30ia4$1wKT8o|%%{q2s)48+O{jqh{O$iYh!Q609dR@^;SaoYrBgpPVeb{*3 z5ldJ<=1za2ECR;ugvoC4Rjn<$0Jyz!)$ddlp8#@Pa58LXsJ**5XL3|iu^ds)80WVJWqUEv$6PHlt$a&;U)JUxg~dGdU~}_HS9(EgqonL z&74hk0G&O1hZ-gZrnVaSzV;Q3i=WHM|MmMotxj2meFj`grGR)T+?cYuS&gU7w8ea!gzdwAN`HaRj~WcE4vUS9wJ002ovPDHLkV1mv{ BiC_Q# literal 0 HcmV?d00001 diff --git a/icons/add_group_void.png b/icons/add_group_void.png new file mode 100644 index 0000000000000000000000000000000000000000..9975b74146ec4fc213af773a9376472c3d814213 GIT binary patch literal 3079 zcmc&$c{CJiAD+P=YnDQ`N|tml)mW}9(^v;#NWx@^%tQv+MwX_b;zn1pW#5<2E)fPz z3u0zarm+sG1{r2TOc|PQ?svcMkNchbo%`qap7VQu=UvWo-gDmH^ZcG);f^-qB8Nl( z0D!nH%+iII>v&NwEXbR;wS_c!Ng%@9)>W7{;)SoI@YegnU>*?wfR5Vl0gS1FMDsQk zBCXsb&xax-F_*&w02mBLCotq%g#YER0G-fqQ~}fE5C9;0+tw258dJDNyOwr|rIHhEM<(+?Bg+j9r^KfqKmF#dxq(eCC z_wK$2zQ~UWl2RMDv+&KtMu^HI8N{|$i)yFRH?fv+TIl_Dh}P2|V!o$agxp0++GGPwoA>P4wQEB2LUvvL(Xg}3dF{VBFI);x8d@23WQvrbr@bmC5zdtbfm?2GU z;_HxIX4l->qGNL8i4Tu*%{F&tCKk*BdTqP_@w(0vV&@J|D;{7#eXlf_2op#e04Kmp zKVtOa@P%7IL!dYz_pMkD%s!e32pA@jkRFX;Eb_#SFG+OZc8#vh}n!_ze056^yEl-f(Xf%)WU zTPg5(_oO~ZUgm^u1iOkkx4=y2!@}NB!!gf>Xxuesb2sP1rM)|?QQjD~PZ!4wgH`M_ zePewP>b1%w&i2x1xza^(OGUzkSpM3&WGT8ldFNWHb3?L6-I;l;`|;TL&W$7)`X@-s z+%e9%A0+O06rm=utCPZC50Ha4I#JHcD^5PFw*=mvnQIA*e>06e~$k&_YEyoq!Acp&=aKK*d7VF57U6n91Ok+0x=^>@2C8Z(@A!;EEl_ z$JtlWuq_oA%C{t)Fcz!g4*OcBdou)ImaEEQSz-njfo-=WQ$osqwC18K(;u6By(mK~ zd1{$p`o*PT&+F`kE=gUhU6UaT?r{ZnjRiJjI5yo={3ju5-jt|i31twFE0q9MXCW~x zQp``%Ex7Ix!;BYl&)5LSb+#URk1m`Q1xNhacUiAs*?i|}xo%i6fC`Z8;@Bvpmu1a? zat4OU^h2*R21=JF9GRr>!!Bd+dvPLL4&{uFBQXOs77Xv4Z(3JuZW`}Eb~vIHX}tU3 ztJXb%e-r;N1SC-B8&3%uW>yT_Kg%AoU_Il)BTUs{BIbZ z4*kClE5FZIsh>rKj zrK)R6-Q>ae{qDj_KIIyN^lBn0*eomw|IDYnBegn$BI1Yi5h^Z^JmSzT8$%{(ASpM~ zpV&0+@A|UK*Ah=_`D`Jd;HbQnDRkbe7s17>dAbq^k|FM_{(?a`iA0#0jZ2crf>IUA`X{C%L^$3FZ{ z(BvP(CzFg)7>?Ez(1rEM~OvULg5zqG-=ldVx>YCy6 z`jA?`PYu&C?>t0;qLBllyxPgVrUb2nR1sO%suS~zbOygaR}rc=aa-mFS~aTWT9l|F7N40Ti7y<5yk{C}*D8S9Qg3P6_Qkw_ znGxKiD@L09Kag#5duTm!%`@@K*)AK=HN9PJW91G6G0c2pG7-GUbSjvl6Zl{Kkh`iR zvAVi^9p4u;kN6_#ecB_fOUQ{|bT+|od4qB*jQxyn9A|YlA}=|jb4F}d?9vCO;%4@A zg~WagSH5$O)W>&DBi63nb#NI(XpnBt9!RnbeXlmbO+4D+kv*d`f)gj4VD`0-S&OUy zA}~VKPt^}kwpkC$P-8;mY}H;9yqSydf^p=jiADmA**VHRk<~Pja<(C-JSh`;dVDk@@j0H^Jvg^W9DxmXw-y9Vxp&l0UfoCHhJKo z$(dtNw+GA_0Z<;Fn?IqpLyZVHuPKw`-5fGCrd2scKl3*cIQ+y*d!Z$zxnAzahnTUx znoI|KOn?1c7<_Y zQ*B)Y7Vd~bbeqI4H8o4DwsH$B=sG(Y2e$1gXur+mM@mG(pmCIYSCIOI`-B^oy48;; qgVmn6iIlmJ4NVNTT2Jj9i~F-Z*UaFT1t!n518l7vEt|}J6aNXbU%7Dr literal 0 HcmV?d00001 diff --git a/icons/add_group_void.svg b/icons/add_group_void.svg new file mode 100644 index 0000000..564b4d9 --- /dev/null +++ b/icons/add_group_void.svg @@ -0,0 +1,130 @@ + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + group + + + + diff --git a/icons/copy.png b/icons/copy.png new file mode 100644 index 0000000000000000000000000000000000000000..d7c81dafadc31c76af5643d11b47249fde613f02 GIT binary patch literal 5112 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 z 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 literal 0 HcmV?d00001 diff --git a/icons/folder_add_16.png b/icons/folder_add_16.png new file mode 100644 index 0000000000000000000000000000000000000000..213aabda858aba571544d8298340a0ebee1814d6 GIT binary patch literal 761 zcmVY7MozPAV@{3DCA&i(Th|NQ9S6OdeE~6Paf=D#e+gU3F5(m;=zMeu#_TJ zYKgIFOlunRwcGueb(1C}v84|lGsEzHZ{GjCA*N}fW{>Xn*AIKxrF#pcw&(x99Jw&L zXYku+kDC(h0GP}~HodfKu-kv2DM=Z&1F)qjh$|^jMzP=S1p#S*v&uU2dVBR*PHUG} zX95H4LN0IUjyei?z?cWd`JjU*H0W1mXbYpH+ zMQ}HO&k>wb_!W;KGGt{Nh^ZpJD5K5GAzv!NGysicpb8x36Uz{6c4$hj4p6k>h)lCc zCR(B9bWozAJR6#-V`cF#G^V4)#Zc4@d`%k|4uUGEmfNO{$nPVe2fx7cIayq{84V z5$y#2hz##Q0g9Bu+G-kfOGoO*JRI$wdI`FQcZH08)Y+GZH)=u>3E~S!Ao?~DIJ*X| zU};fQu`v$}`&}>^+F3SQNbA$`ALOakZ(;QF32ldW#~p5`+Ubj9veZlNVFy35jSCIp_(B*rBl1Ayx&{UqEWVapKlc z)Zdx>gh3gn`y8-Tz|3qGZHW+$?dwOd%iGldh4-@+5z8{$s}pHHxN)$UEo%6cS0E6I r4$%gVBk(!}2s{Btx4rRO{}W&U?;aCbogG0e00000NkvXXu0mjf?Wb1K literal 0 HcmV?d00001 diff --git a/icons/inter_add.png b/icons/inter_add.png new file mode 100644 index 0000000000000000000000000000000000000000..7e07d262bbf2c08731dcb7c49e7f25a24f8f139f GIT binary patch literal 1822 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 A1ONa4 literal 0 HcmV?d00001 diff --git a/icons/load.png b/icons/load.png new file mode 100644 index 0000000000000000000000000000000000000000..b9fbbeaacb013f10f47d7ba1e4e648355d460aba GIT binary patch literal 10501 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