diff --git a/.appveyor.yml b/.appveyor.yml index 9f51d4dbb2..83cb2f5059 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -9,7 +9,7 @@ configuration: Release install: - git submodule update --init - cd dependencies - - download-dependencies.py discount qwt zeromq cppzmq msgpack-c katex tclap fmi4c xerces asio zlib libzip dcplib + - download-dependencies.py discount qwt zeromq cppzmq msgpack-c katex tclap fmi4c xerces asio zlib libzip dcplib ssp4c - cd .. build_script: # Remove sh.exe from PATH so that CMake works @@ -28,6 +28,7 @@ build_script: - setupQwt.bat - setupZeroMQ.bat - setupMsgpack.bat + - setupSsp4c.bat - call setHopsanBuildPaths.bat - cd .. - mkdir build diff --git a/.github/workflows/ci-build.yaml b/.github/workflows/ci-build.yaml index 04fe127a9c..fd86150961 100644 --- a/.github/workflows/ci-build.yaml +++ b/.github/workflows/ci-build.yaml @@ -38,7 +38,7 @@ jobs: - name: Download dependencies shell: cmd working-directory: dependencies - run: python download-dependencies.py discount tclap qwt fmi4c xerces asio zlib libzip dcplib + run: python download-dependencies.py discount tclap qwt fmi4c xerces asio zlib libzip dcplib ssp4c - name: Build dependencies shell: cmd @@ -53,6 +53,7 @@ jobs: call setupXerces.bat call setupDCPLib.bat call setupQwt.bat + call setupSsp4c.bat - name: Configure working-directory: hopsan-build @@ -94,7 +95,7 @@ jobs: - name: Download dependencies shell: bash working-directory: dependencies - run: ./download-dependencies.py discount tclap fmi4c xerces asio dcplib libzip qwt + run: ./download-dependencies.py discount tclap fmi4c xerces asio dcplib libzip qwt ssp4c - name: Build dependencies shell: bash @@ -107,6 +108,7 @@ jobs: ./setupXerces.sh ./setupDCPLib.sh ./setupQwt.sh + ./setupSsp4c.sh - name: Configure working-directory: hopsan-build diff --git a/HopsanCore/include/HopsanCoreGitVersion.h b/HopsanCore/include/HopsanCoreGitVersion.h index 9bfa58b4c7..62e4b43236 100644 --- a/HopsanCore/include/HopsanCoreGitVersion.h +++ b/HopsanCore/include/HopsanCoreGitVersion.h @@ -2,6 +2,6 @@ // $Id$ #ifndef HOPSANCOREGITVERSION_H_INCLUDED #define HOPSANCOREGITVERSION_H_INCLUDED -#define HOPSANCORE_COMMIT_HASH 250596a1b -#define HOPSANCORE_COMMIT_TIMESTAMP 20200515.1608 +#define HOPSANCORE_COMMIT_HASH f6800a955 +#define HOPSANCORE_COMMIT_TIMESTAMP 20260217.1325 #endif diff --git a/HopsanGUI/CMakeLists.txt b/HopsanGUI/CMakeLists.txt index 4b59e0e047..7bc5b10cf2 100644 --- a/HopsanGUI/CMakeLists.txt +++ b/HopsanGUI/CMakeLists.txt @@ -20,6 +20,7 @@ include(${CMAKE_CURRENT_LIST_DIR}/../dependencies/discount.cmake) include(${CMAKE_CURRENT_LIST_DIR}/../dependencies/katex.cmake) include(${CMAKE_CURRENT_LIST_DIR}/../dependencies/libzip.cmake) include(${CMAKE_CURRENT_LIST_DIR}/../dependencies/xerces.cmake) +include(${CMAKE_CURRENT_LIST_DIR}/../dependencies/ssp4c.cmake) set(target_name hopsangui) @@ -39,7 +40,7 @@ if(NOT CMAKE_BUILD_TYPE MATCHES Debug) endif() target_link_libraries(${target_name} Qt5::Core Qt5::Gui Qt5::Widgets Qt5::Xml Qt5::Svg Qt5::Network Qt5::Test Qt5::PrintSupport - qwt hopsancore hopsangeneratorgui symhop ops hopsandcp xercesc ) + qwt hopsancore hopsangeneratorgui symhop ops hopsandcp xercesc ssp4c ) if(UNIX) target_link_libraries(${target_name} Qt5::WebEngineWidgets) endif() diff --git a/HopsanGUI/GUIConnector.cpp b/HopsanGUI/GUIConnector.cpp index 7d1530b600..946eccc7e6 100644 --- a/HopsanGUI/GUIConnector.cpp +++ b/HopsanGUI/GUIConnector.cpp @@ -472,6 +472,13 @@ QPointF Connector::getEndPoint() return mPoints.last(); } + +//! @brief Returns all points of the connector +QVector Connector::getPoints() const +{ + return mPoints; +} + //! @brief Returns the name of the start port of a connector //! @see getEndPortName() QString Connector::getStartPortName() const @@ -1258,8 +1265,11 @@ void Connector::setPointsAndGeometries(const QVector &rPoints, const QS mGeometries.push_back(Horizontal); else if(mPoints[i].y() == mPoints[i+1].y()) mGeometries.push_back(Vertical); - else + else { + qDebug() << mPoints[i].x() << " != " << mPoints[i+1].x(); + qDebug() << mPoints[i].y() << " != " << mPoints[i+1].y(); mGeometries.push_back(Diagonal); + } } else { diff --git a/HopsanGUI/GUIConnector.h b/HopsanGUI/GUIConnector.h index 918179d14a..c319aad2d3 100644 --- a/HopsanGUI/GUIConnector.h +++ b/HopsanGUI/GUIConnector.h @@ -81,6 +81,7 @@ class Connector : public QGraphicsWidget void setPointsAndGeometries(const QVector &rPoints, const QStringList &rGeometries); QPointF getStartPoint(); QPointF getEndPoint(); + QVector getPoints() const; ConnectorLine *getLine(int line); ConnectorLine *getLastLine(); int getNumberOfLines(); diff --git a/HopsanGUI/GUIObjects/GUIComponent.cpp b/HopsanGUI/GUIObjects/GUIComponent.cpp index c8b695c0da..f345cab0e4 100644 --- a/HopsanGUI/GUIObjects/GUIComponent.cpp +++ b/HopsanGUI/GUIObjects/GUIComponent.cpp @@ -50,6 +50,7 @@ #include "PlotWindow.h" #include "Widgets/ModelWidget.h" #include "LibraryHandler.h" +#include "UndoStack.h" Component::Component(QPointF position, double rotation, ModelObjectAppearance* pAppearanceData, SystemObject *pParentSystem, SelectionStatusEnumT startSelected, GraphicsTypeEnumT gfxType) @@ -461,6 +462,272 @@ QDomElement Component::saveGuiDataToDomElement(QDomElement &rDomElement) } +//========== ResizableComponent ========== + +ResizableComponent::ResizableComponent(QPointF position, double rotation, ModelObjectAppearance* pAppearanceData, SystemObject *pParentSystem, SelectionStatusEnumT startSelected, GraphicsTypeEnumT gfxType) + : Component(position, rotation, pAppearanceData, pParentSystem, startSelected, gfxType) +{ + // Initialize resizing members + mResizeTop = false; + mResizeBottom = false; + mResizeLeft = false; + mResizeRight = false; + mIsResizing = false; + mWidthBeforeResize = 0; + mHeightBeforeResize = 0; + + // Hide the icon and create a rectangle instead + mpIcon->hide(); + + // Create rectangle with default size + mpRect = new QGraphicsRectItem(0, 0, 100, 80, this); + QPen borderPen; + borderPen.setWidth(2); + borderPen.setColor(Qt::black); + mpRect->setPen(borderPen); + mpRect->setBrush(QBrush(Qt::lightGray)); + mpRect->show(); + + // Create text item in the center + mpText = new QGraphicsTextItem(this); + mpText->setPlainText("Resizable"); + mpText->setAcceptHoverEvents(false); + QFont textFont = mpText->font(); + textFont.setPointSize(10); + mpText->setFont(textFont); + mpText->show(); + + // Center text in rectangle + QRectF textRect = mpText->boundingRect(); + mpText->setPos((100 - textRect.width()) / 2, (80 - textRect.height()) / 2); + + // Resize the widget to match rectangle size + this->resize(mpRect->boundingRect().width(), mpRect->boundingRect().height()); + this->refreshSelectionBoxSize(); +} + + +//! @brief Refreshes the appearance of the object +void ResizableComponent::refreshAppearance() +{ + //! @todo should make sure we only do this if we really need to resize (after icon change) + QPointF centerPos = this->getCenterPos(); //Remember center pos for resize + this->setCenterPos(centerPos); //Re-set center pos after resize + + this->refreshDisplayName(); + this->refreshExternalPortsAppearanceAndPosition(); +} + +//! @brief Set the outline color of the rectangle +void ResizableComponent::setRectColor(const QColor &color) +{ + if(mpRect) + { + QPen pen = mpRect->pen(); + pen.setColor(color); + mpRect->setPen(pen); + } +} + + +//! @brief Set the fill color of the rectangle +void ResizableComponent::setRectFillColor(const QColor &color) +{ + if(mpRect) + { + mpRect->setBrush(QBrush(color)); + } +} + + +//! @brief Set the text in the center of the rectangle +void ResizableComponent::setText(const QString &text) +{ + if(mpText) + { + mpText->setPlainText(text); + // Re-center the text + QRectF rectSize = mpRect->rect(); + QRectF textRect = mpText->boundingRect(); + mpText->setPos((rectSize.width() - textRect.width()) / 2, (rectSize.height() - textRect.height()) / 2); + } +} + + +//! @brief Set the component size (width and height) +void ResizableComponent::setComponentSize(double width, double height) +{ + if(width > 0 && height > 0 && mpRect) + { + this->prepareGeometryChange(); + mpRect->setRect(0, 0, width, height); + this->resize(width, height); + refreshSelectionBoxSize(); + + // Refresh port positions for the new component size + for(int i = 0; i < mPortListPtrs.size(); ++i) + { + createRefreshExternalPort(mPortListPtrs[i]->getName()); + } + } +} + + +//! @brief Refresh the selection box to match rectangle size +void ResizableComponent::refreshSelectionBoxSize() +{ + mpSelectionBox->setSize(0.0, 0.0, mpRect->rect().width(), mpRect->rect().height()); +} + + +//! @brief Handle hover movement for edge-based resizing +void ResizableComponent::hoverMoveEvent(QGraphicsSceneHoverEvent *event) +{ + Component::hoverMoveEvent(event); + + this->setCursor(Qt::ArrowCursor); + mResizeLeft = false; + mResizeRight = false; + mResizeTop = false; + mResizeBottom = false; + + int resLim = 5; + + if(event->pos().x() > boundingRect().left() && event->pos().x() < boundingRect().left()+resLim) + { + mResizeLeft = true; + } + if(event->pos().x() > boundingRect().right()-resLim && event->pos().x() < boundingRect().right()) + { + mResizeRight = true; + } + if(event->pos().y() > boundingRect().top() && event->pos().y() < boundingRect().top()+resLim) + { + mResizeTop = true; + } + if(event->pos().y() > boundingRect().bottom()-resLim && event->pos().y() < boundingRect().bottom()) + { + mResizeBottom = true; + } + + if( (mResizeLeft && mResizeTop) || (mResizeRight && mResizeBottom) ) + this->setCursor(Qt::SizeFDiagCursor); + else if( (mResizeTop && mResizeRight) || (mResizeBottom && mResizeLeft) ) + this->setCursor(Qt::SizeBDiagCursor); + else if(mResizeLeft || mResizeRight) + this->setCursor(Qt::SizeHorCursor); + else if(mResizeTop || mResizeBottom) + this->setCursor(Qt::SizeVerCursor); +} + + +//! @brief Handle mouse press for starting resize operation +void ResizableComponent::mousePressEvent(QGraphicsSceneMouseEvent *event) +{ + if(mResizeLeft || mResizeRight || mResizeTop || mResizeBottom) + { + mPosBeforeResize = this->pos(); + mWidthBeforeResize = boundingRect().width(); + mHeightBeforeResize = boundingRect().height(); + mIsResizing = true; + } + else + { + mIsResizing = false; + } + Component::mousePressEvent(event); +} + + +//! @brief Handle mouse move for resizing the component +void ResizableComponent::mouseMoveEvent(QGraphicsSceneMouseEvent *event) +{ + Component::mouseMoveEvent(event); + + if (getModelLockLevel()==NotLocked && mIsResizing) + { + // Calculate the desired new width and height + double desiredWidth = mWidthBeforeResize; + double desiredHeight = mHeightBeforeResize; + QPointF newPos = mPosBeforeResize; + + // Handle left resize + if(mResizeLeft) + { + double deltaX = this->pos().x() - mPosBeforeResize.x(); + desiredWidth = qMax(5.0, mWidthBeforeResize - deltaX); + newPos.setX(mPosBeforeResize.x() + (mWidthBeforeResize - desiredWidth)); + } + // Handle right resize + else if(mResizeRight) + { + double deltaX = this->pos().x() - mPosBeforeResize.x(); + desiredWidth = qMax(5.0, mWidthBeforeResize + deltaX); + newPos.setX(mPosBeforeResize.x()); + } + + // Handle top resize + if(mResizeTop) + { + double deltaY = this->pos().y() - mPosBeforeResize.y(); + desiredHeight = qMax(5.0, mHeightBeforeResize - deltaY); + newPos.setY(mPosBeforeResize.y() + (mHeightBeforeResize - desiredHeight)); + } + // Handle bottom resize + else if(mResizeBottom) + { + double deltaY = this->pos().y() - mPosBeforeResize.y(); + desiredHeight = qMax(5.0, mHeightBeforeResize + deltaY); + newPos.setY(mPosBeforeResize.y()); + } + + // Update rectangle size based on the desired new width and height + this->prepareGeometryChange(); + mpRect->setRect(0, 0, desiredWidth, desiredHeight); + this->setPos(newPos); + + // Resize the widget to match the rectangle size + this->resize(desiredWidth, desiredHeight); + + // Re-center the text + if(mpText) + { + QRectF textRect = mpText->boundingRect(); + mpText->setPos((desiredWidth - textRect.width()) / 2, (desiredHeight - textRect.height()) / 2); + } + + refreshSelectionBoxSize(); + mpSelectionBox->setActive(); + } +} + + +//! @brief Handle mouse release to register resize in undo stack +void ResizableComponent::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) +{ + Component::mouseReleaseEvent(event); + + if(mResizeLeft || mResizeRight || mResizeTop || mResizeBottom) + { + // Refresh port positions for the new component size + for(int i = 0; i < mPortListPtrs.size(); ++i) + { + createRefreshExternalPort(mPortListPtrs[i]->getName()); + } + + if(mWidthBeforeResize != boundingRect().width() || mHeightBeforeResize != boundingRect().height()) + { + mpParentSystemObject->getUndoStackPtr()->newPost(); + } + mResizeLeft = false; + mResizeRight = false; + mResizeTop = false; + mResizeBottom = false; + mIsResizing = false; + } +} + + void ScopeComponent::mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event) { QGraphicsWidget::mouseDoubleClickEvent(event); diff --git a/HopsanGUI/GUIObjects/GUIComponent.h b/HopsanGUI/GUIObjects/GUIComponent.h index 0210ffa3d7..7d4e6b3e22 100644 --- a/HopsanGUI/GUIObjects/GUIComponent.h +++ b/HopsanGUI/GUIObjects/GUIComponent.h @@ -80,6 +80,42 @@ private slots: void createPorts(); }; + +class ResizableComponent : public Component +{ + Q_OBJECT + +public: + ResizableComponent(QPointF position, double rotation, ModelObjectAppearance* pAppearanceData, SystemObject *pParentSystem, SelectionStatusEnumT startSelected = Deselected, GraphicsTypeEnumT gfxType = UserGraphics); + + void setRectColor(const QColor &color); + void setRectFillColor(const QColor &color); + void setText(const QString &text); + void setComponentSize(double width, double height); + void refreshAppearance() override; + +protected: + virtual void hoverMoveEvent(QGraphicsSceneHoverEvent *event) override; + virtual void mousePressEvent(QGraphicsSceneMouseEvent *event) override; + virtual void mouseMoveEvent(QGraphicsSceneMouseEvent *event) override; + virtual void mouseReleaseEvent(QGraphicsSceneMouseEvent *event) override; + virtual void refreshSelectionBoxSize() override; + +private: + // Resizing members + bool mResizeTop; + bool mResizeBottom; + bool mResizeLeft; + bool mResizeRight; + bool mIsResizing; + QPointF mPosBeforeResize; + double mWidthBeforeResize; + double mHeightBeforeResize; + + QGraphicsRectItem *mpRect; + QGraphicsTextItem *mpText; +}; + class ScopeComponent final : public Component { Q_OBJECT diff --git a/HopsanGUI/GUIObjects/GUIContainerObject.cpp b/HopsanGUI/GUIObjects/GUIContainerObject.cpp index 05a3fe783d..67e3747ea5 100644 --- a/HopsanGUI/GUIObjects/GUIContainerObject.cpp +++ b/HopsanGUI/GUIObjects/GUIContainerObject.cpp @@ -597,7 +597,7 @@ void SystemObject::renameExternalPort(const QString oldName, const QString newNa //! @brief Helper function that allows calling addGUIModelObject with typeName instead of appearance data -ModelObject* SystemObject::addModelObject(QString fullTypeName, QPointF position, double rotation, SelectionStatusEnumT startSelected, NameVisibilityEnumT nameStatus, UndoStatusEnumT undoSettings) +ModelObject* SystemObject::addModelObject(QString fullTypeName, QPointF position, double rotation, SelectionStatusEnumT startSelected, NameVisibilityEnumT nameStatus, UndoStatusEnumT undoSettings, ResizbleEnumT resizable) { ModelObjectAppearance *pAppearanceData = gpLibraryHandler->getModelObjectAppearancePtr(fullTypeName).data(); @@ -632,7 +632,7 @@ ModelObject* SystemObject::addModelObject(QString fullTypeName, QPointF position return pObj; } } - return addModelObject(pAppearanceData, position, rotation, startSelected, nameStatus, undoSettings); + return addModelObject(pAppearanceData, position, rotation, startSelected, nameStatus, undoSettings, resizable); } @@ -642,7 +642,7 @@ ModelObject* SystemObject::addModelObject(QString fullTypeName, QPointF position //! @param name will be the name of the component. //! @returns a pointer to the created and added object //! @todo only modelobjects for now -ModelObject* SystemObject::addModelObject(ModelObjectAppearance *pAppearanceData, QPointF position, double rotation, SelectionStatusEnumT startSelected, NameVisibilityEnumT nameStatus, UndoStatusEnumT undoSettings) +ModelObject* SystemObject::addModelObject(ModelObjectAppearance *pAppearanceData, QPointF position, double rotation, SelectionStatusEnumT startSelected, NameVisibilityEnumT nameStatus, UndoStatusEnumT undoSettings, ResizbleEnumT resizability) { // Deselect all other components and connectors emit deselectAllGUIObjects(); @@ -665,6 +665,15 @@ ModelObject* SystemObject::addModelObject(ModelObjectAppearance *pAppearanceData { pNewModelObject = new ScopeComponent(position, rotation, pAppearanceData, this, startSelected, mGfxType); } + else if (componentTypeName == "FMIWrapper") + { + if(resizability == Resizable) { + pNewModelObject = new ResizableComponent(position, rotation, pAppearanceData, this, startSelected, mGfxType); + } + else { + pNewModelObject = new Component(position, rotation, pAppearanceData, this, startSelected, mGfxType); + } + } else //Assume some standard component type { pNewModelObject = new Component(position, rotation, pAppearanceData, this, startSelected, mGfxType); diff --git a/HopsanGUI/GUIObjects/GUIContainerObject.h b/HopsanGUI/GUIObjects/GUIContainerObject.h index a2a4f53186..cf724985dc 100644 --- a/HopsanGUI/GUIObjects/GUIContainerObject.h +++ b/HopsanGUI/GUIObjects/GUIContainerObject.h @@ -170,8 +170,8 @@ class SystemObject : public ModelObject void takeOwnershipOf(QList &rModeObjectlist, QList &rWidgetList); //GUIModelObject methods - ModelObject *addModelObject(QString fullTypeName, QPointF position, double rotation=0, SelectionStatusEnumT startSelected = Deselected, NameVisibilityEnumT nameStatus = UseDefault, UndoStatusEnumT undoSettings = Undo); - ModelObject *addModelObject(ModelObjectAppearance* pAppearanceData, QPointF position, double rotation=0, SelectionStatusEnumT startSelected = Deselected, NameVisibilityEnumT nameStatus = UseDefault, UndoStatusEnumT undoSettings = Undo); + ModelObject *addModelObject(QString fullTypeName, QPointF position, double rotation=0, SelectionStatusEnumT startSelected = Deselected, NameVisibilityEnumT nameStatus = UseDefault, UndoStatusEnumT undoSettings = Undo, ResizbleEnumT resizable = NotResizable); + ModelObject *addModelObject(ModelObjectAppearance* pAppearanceData, QPointF position, double rotation=0, SelectionStatusEnumT startSelected = Deselected, NameVisibilityEnumT nameStatus = UseDefault, UndoStatusEnumT undoSettings = Undo, ResizbleEnumT resizable = NotResizable); void deleteModelObject(const QString &rObjectName, UndoStatusEnumT undoSettings=Undo); void renameModelObject(QString oldName, QString newName, UndoStatusEnumT undoSettings=Undo); diff --git a/HopsanGUI/HopsanGUI.pro b/HopsanGUI/HopsanGUI.pro index b524f95d69..76b20dda01 100644 --- a/HopsanGUI/HopsanGUI.pro +++ b/HopsanGUI/HopsanGUI.pro @@ -38,6 +38,11 @@ isEqual(QT_MAJOR_VERSION, 5){ TARGET = $${TARGET}$${DEBUG_EXT} +#-------------------------------------------------------- +# Set the ssp4c paths +include($${PWD}/../dependencies/ssp4c.pri) +#-------------------------------------------------------- + #-------------------------------------------------------- # Set the QWT paths include($${PWD}/../dependencies/qwt.pri) @@ -219,6 +224,9 @@ SOURCES += main.cpp \ ProgressBarThread.cpp \ GUIPortAppearance.cpp \ GUIConnectorAppearance.cpp \ + Widgets/SSPWidget.cpp \ + Widgets/SSVEditorWidget.cpp \ + Widgets/SSMEditorWidget.cpp \ Widgets/SystemParametersWidget.cpp \ PlotWindow.cpp \ GUIObjects/GUIWidgets.cpp \ @@ -297,6 +305,9 @@ HEADERS += MainWindow.h \ GUIPort.h \ Widgets/MessageWidget.h \ InitializationThread.h \ + Widgets/SSPWidget.h \ + Widgets/SSVEditorWidget.h \ + Widgets/SSMEditorWidget.h \ version_gui.h \ Dialogs/OptionsDialog.h \ UndoStack.h \ diff --git a/HopsanGUI/MainWindow.cpp b/HopsanGUI/MainWindow.cpp index 3ec746b136..ede7397eeb 100644 --- a/HopsanGUI/MainWindow.cpp +++ b/HopsanGUI/MainWindow.cpp @@ -80,6 +80,7 @@ #include "Widgets/DataExplorer.h" #include "Widgets/FindWidget.h" #include "Widgets/TextEditorWidget.h" +#include "Widgets/SSPWidget.h" #include "Dialogs/OptionsDialog.h" #include "Dialogs/AboutDialog.h" @@ -104,6 +105,7 @@ ModelHandler *gpModelHandler = 0; PlotWidget2 *gpPlotWidget = 0; CentralTabWidget *gpCentralTabWidget = 0; SystemParametersWidget *gpSystemParametersWidget = 0; +SSPWidget *gpSSPWidget = 0; UndoWidget *gpUndoWidget = 0; LibraryHandler *gpLibraryHandler = 0; HelpPopUpWidget *gpHelpPopupWidget = 0; @@ -275,6 +277,11 @@ void MainWindow::createContents() gpSystemParametersWidget = mpSystemParametersWidget; mpSystemParametersWidget->setVisible(false); + //Create the SSP widget and hide it + mpSSPWidget = new SSPWidget(this); + gpSSPWidget = mpSSPWidget; + //mpSSPWidget->setVisible(false); + // Create the HVC Widget mpHVCWidget = new HVCWidget(this); mpHVCWidget->setVisible(false); @@ -296,6 +303,13 @@ void MainWindow::createContents() addDockWidget(Qt::RightDockWidgetArea, mpSystemParametersDock); mpSystemParametersDock->hide(); + //Create the SSP dock widget and hide it + mpSSPWidgetDock = new QDockWidget(tr("SSP Explorer"), this); + mpSSPWidgetDock->setAllowedAreas((Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea)); + mpSSPWidgetDock->setWidget(mpSSPWidget); + addDockWidget(Qt::RightDockWidgetArea, mpSSPWidgetDock); + //mpSSPWidgetDock->hide(); + //Create the undo dock widget and hide it mpUndoWidgetDock = new QDockWidget(tr("Undo History"), this); mpUndoWidgetDock->setAllowedAreas(Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea); @@ -1327,7 +1341,12 @@ void MainWindow::openRecentModel() qDebug() << "Trying to open " << action->text(); if (action) { - mpModelHandler->loadModel(action->text()); + if(QFileInfo(action->text()).suffix() == "ssp") { + mpModelHandler->loadSsp(action->text()); + } + else { + mpModelHandler->loadModel(action->text()); + } } } diff --git a/HopsanGUI/MainWindow.h b/HopsanGUI/MainWindow.h index a8438969eb..309323dd7c 100644 --- a/HopsanGUI/MainWindow.h +++ b/HopsanGUI/MainWindow.h @@ -66,6 +66,7 @@ class MainWindowLineEdit; class DataExplorer; class FindWidget; class HelpPopUpWidget; +class SSPWidget; class SimulationTimeEdit : public QWidget @@ -132,6 +133,7 @@ class MainWindow : public QMainWindow WelcomeWidget *mpWelcomeWidget; LibraryWidget *mpLibraryWidget; SystemParametersWidget *mpSystemParametersWidget; + SSPWidget *mpSSPWidget; QStatusBar *mpStatusBar; //Not used, but gives some nice extra space at bottom :) QGridLayout *mpCentralGridLayout; @@ -291,6 +293,7 @@ private slots: QDockWidget *mpPlotWidgetDock; QDockWidget *mpUndoWidgetDock; QDockWidget *mpSystemParametersDock; + QDockWidget *mpSSPWidgetDock; QWidget *mpCentralWidget; QGridLayout *mpTabgrid; diff --git a/HopsanGUI/ModelHandler.cpp b/HopsanGUI/ModelHandler.cpp index f8919b34ed..fa3adcaa70 100644 --- a/HopsanGUI/ModelHandler.cpp +++ b/HopsanGUI/ModelHandler.cpp @@ -46,6 +46,7 @@ #include "MessageHandler.h" #include "MainWindow.h" #include "ModelHandler.h" +#include "GUIPort.h" #include "SimulationThreadHandler.h" #include "version_gui.h" #include "Widgets/DebuggerWidget.h" @@ -56,6 +57,7 @@ #include "Widgets/ProjectTabWidget.h" #include "Widgets/DataExplorer.h" #include "Widgets/PlotWidget2.h" +#include "Widgets/SSPWidget.h" #include "Widgets/TextEditorWidget.h" #include "Utilities/GUIUtilities.h" @@ -251,9 +253,14 @@ void ModelHandler::loadModel() QDir fileDialogOpenDir; QStringList modelFileNames = QFileDialog::getOpenFileNames(gpMainWindowWidget, tr("Choose Model File"), gpConfig->getStringSetting(cfg::dir::loadmodel), - tr("Hopsan Model Files (*.hmf *.xml)")); + tr("Hopsan Model Files (*.hmf *.xml *.ssp)")); for(const QString &modelFileName : modelFileNames) { - loadModel(modelFileName); + if(QFileInfo(modelFileName).suffix() == "ssp") { + loadSsp(modelFileName); + } + else { + loadModel(modelFileName); + } QFileInfo fileInfo = QFileInfo(modelFileName); gpConfig->setStringSetting(cfg::dir::loadmodel, fileInfo.absolutePath()); } @@ -412,6 +419,11 @@ TextEditorWidget *ModelHandler::loadTextFile(QString fileName) return pNewEditor; } +void ModelHandler::loadSsp(QString fileName) +{ + gpSSPWidget->addSSP(QFileInfo(fileName)); +} + bool ModelHandler::closeModelByTabIndex(int tabIdx, bool force) { diff --git a/HopsanGUI/ModelHandler.h b/HopsanGUI/ModelHandler.h index 882faa13b2..ecc7c54036 100644 --- a/HopsanGUI/ModelHandler.h +++ b/HopsanGUI/ModelHandler.h @@ -98,6 +98,8 @@ class ModelHandler : public QObject TextEditorWidget *loadTextFile(QString scriptFileName); SimulationThreadHandler *mpSimulationThreadHandler; + void loadSsp(QString fileName); + public slots: ModelWidget *addNewModel(QString modelName="Untitled", ModelHandler::LoadOptions options=NoLoadOptions); ModelWidget *addNewDcpModel(); diff --git a/HopsanGUI/PlotArea.cpp b/HopsanGUI/PlotArea.cpp index 6c67f6c5bb..c54b0a9604 100644 --- a/HopsanGUI/PlotArea.cpp +++ b/HopsanGUI/PlotArea.cpp @@ -1015,21 +1015,26 @@ void PlotArea::dragMoveEvent(QDragMoveEvent *event) void PlotArea::dropEvent(QDropEvent *event) { + qDebug() << "DropEvent"; // Don't accept drag events to FFT and Bode plots if( mpParentPlotTab->getPlotTabType() == XYPlotType ) { + qDebug() << "Correct plot type"; QWidget::dropEvent(event); mpPainterWidget->clearRect(); if (event->mimeData()->hasText()) { + qDebug() << "Has text"; QString mimeText = event->mimeData()->text(); if(mimeText.startsWith("HOPSANPLOTDATA:")) { + qDebug() << "Correct text"; qDebug() << mimeText; QStringList fields = mimeText.split(":"); if (fields.size() > 3) { + qDebug() << "Correct size"; QString &name = fields[1]; QString &model = fields[3]; bool parseOk = false; @@ -1048,10 +1053,12 @@ void PlotArea::dropEvent(QDropEvent *event) } if (pContainer) { + qDebug() << "Found container"; SharedVectorVariableT data = pContainer->getLogDataHandler()->getVectorVariable(name, gen); // If we have found data then add it to the plot if (data) { + qDebug() << "Found data"; QCursor cursor; if(this->mapFromGlobal(cursor.pos()).y() > getQwtPlot()->canvas()->height()*2.0/3.0+getQwtPlot()->canvas()->y()+10 && getNumberOfCurves() >= 1) { diff --git a/HopsanGUI/Resources.qrc b/HopsanGUI/Resources.qrc index c103487db5..855f04ddfb 100644 --- a/HopsanGUI/Resources.qrc +++ b/HopsanGUI/Resources.qrc @@ -156,6 +156,11 @@ graphics/uiicons/svg/Hopsan-NewDcpModel.svg graphics/builtinCAF/hidden/dcpcomponent.xml graphics/objecticons/dcpcomponent.svg + graphics/uiicons/svg/Hopsan-SSP.svg + graphics/uiicons/svg/Hopsan-SSD.svg + graphics/uiicons/svg/Hopsan-SSV.svg + graphics/uiicons/svg/Hopsan-SSM.svg + graphics/uiicons/svg/Hopsan-FMI.svg ../licenseNoticeGPLv3 diff --git a/HopsanGUI/Widgets/ModelWidget.cpp b/HopsanGUI/Widgets/ModelWidget.cpp index 11cb768ea8..753acf965e 100644 --- a/HopsanGUI/Widgets/ModelWidget.cpp +++ b/HopsanGUI/Widgets/ModelWidget.cpp @@ -58,6 +58,7 @@ #include "ModelHandler.h" #include "ProjectTabWidget.h" #include "SimulationThreadHandler.h" +#include "ssp4c_ssd_component.h" #include "version_gui.h" #include "Widgets/AnimationWidget.h" #include "MessageHandler.h" @@ -71,6 +72,12 @@ #include "LibraryHandler.h" #include "UndoStack.h" +#include "ssp4c_ssd.h" +#include "ssp4c_ssd_system.h" +#include "ssp4c_ssd_element_geometry.h" +#include "ssp4c_ssd_connection.h" +#include "ssp4c_ssd_connection_geometry.h" + //! @class ModelWidget //! @brief The ModelWidget class is a Widget to contain a simulation model //! @@ -162,6 +169,9 @@ ModelWidget::ModelWidget(ModelHandler *pModelHandler, CentralTabWidget *pParentT mpGraphicsView->centerView(); mLastSimulationTime = 0; + + mSsd = nullptr; + mSsp = nullptr; } @@ -196,6 +206,21 @@ void ModelWidget::setMessageHandler(GUIMessageHandler *pMessageHandler) void ModelWidget::setModelType(ModelWidget::ModelType type) { mModelType = type; + + QString modelTypeString; + if(type == ModelWidget::DcpModel) { + modelTypeString = " DCP"; + } + else if(type == ModelWidget::SsdModel) { + modelTypeString = " SSD"; + } + QLabel *pLabel = new QLabel(modelTypeString, this); + pLabel->setStyleSheet("color: Red;"); + QGridLayout *pLayout = qobject_cast(this->layout()); + pLayout->addWidget(pLabel, 1,0,1,1); + pLayout->setRowStretch(2,1); + pLayout->setColumnStretch(1,1); + pLabel->setVisible(type != ModelWidget::HopsanModel); } ModelWidget::ModelType ModelWidget::getModelType() const @@ -318,6 +343,11 @@ void ModelWidget::setLogDataHandler(QSharedPointer pHandler) pHandler->setParentModel(this); } +void ModelWidget::setSsdHandle(ssdHandle *ssd, sspHandle *ssp) +{ + mSsd = ssd; + mSsp = ssp; +} //! @brief Sets last simulation time (only use this from project tab widget!) void ModelWidget::setLastSimulationTime(int time) @@ -981,7 +1011,7 @@ void ModelWidget::importModelParametersFromSsv() if(componentName.isEmpty()) { //Set system parameter CoreParameterData parameter; pSystem->getParameter(parameterName, parameter); - if(ssv::dataTypeTranslator[parameter.mType] != ssvParameter.dataType) { + if(!parameter.mName.isEmpty() && ssv::dataTypeTranslator[parameter.mType] != ssvParameter.dataType) { gpMessageHandler->addWarningMessage("Wrong data type for unit: \""+ssvParameter.name+"\", ignoring value. ("+ssv::dataTypeTranslator[parameter.mType]+" != "+ssvParameter.dataType+")"); } else if(parameter.mType == ssv::datatype::real) { //Handle units for "Real" type parameters only @@ -1012,7 +1042,7 @@ void ModelWidget::importModelParametersFromSsv() } } else { - pSystem->setParameterValue(parameterName, ssvParameter.value); //Ignore units for non-Real parameters + pSystem->setOrAddParameter(parameter, true); //Ignore units for non-Real parameters } } else { //Set component parameter @@ -1329,6 +1359,79 @@ void ModelWidget::openCurrentContainerInNewTab() //! @see loadModel() void ModelWidget::saveModel(SaveTargetEnumT saveAsFlag, SaveContentsEnumT contents) { + if(mModelType == ModelWidget::SsdModel) { + auto rootSystem = ssp4c_ssd_getRootSystem(mSsd); + QMap ssdMap; + for(int c=0; c ssdConnectionMap; + int numConnections = ssp4c_ssd_system_getNumberOfConnections(rootSystem); + for(int c=0; c" + endComp + ":" + endPort; + ssdConnectionMap.insert(key, con); + } + for(const auto &c : mpToplevelSystem->getModelObjects()) { + if(ssdMap.contains(c->getName())) { + ssdComponentHandle* ssdComp = ssdMap[c->getName()]; + ssdElementGeometryHandle* geom = ssp4c_ssd_component_getElementGeometry(ssdComp); + if(geom) { + QPointF center = c->getCenterPos(); + QRectF rect = c->boundingRect(); + double width = rect.width(); + double height = rect.height(); + double x1 = center.x() - width/2 - 2500; + double x2 = center.x() + width/2 - 2500; + double y1 = center.y() - height/2 - 2500; + double y2 = center.y() + height/2 - 2500; + double rotation = c->rotation(); + ssp4c_ssd_elementGeometry_setX1(geom, x1); + ssp4c_ssd_elementGeometry_setX2(geom, x2); + ssp4c_ssd_elementGeometry_setY1(geom, y1); + ssp4c_ssd_elementGeometry_setY2(geom, y2); + ssp4c_ssd_elementGeometry_setRotation(geom, rotation); + } + } + } + for(auto connector : mpToplevelSystem->getSubConnectorPtrs()) { + QString startComp = connector->getStartComponentName(); + QString startPort = connector->getStartPortName(); + QString endComp = connector->getEndComponentName(); + QString endPort = connector->getEndPortName(); + QString key = startComp + ":" + startPort + "->" + endComp + ":" + endPort; + if(ssdConnectionMap.contains(key)) { + ssdConnectionHandle* ssdCon = ssdConnectionMap[key]; + ssdConnectionGeometryHandle* conGeom = ssp4c_ssd_connection_getConnectionGeometry(ssdCon); + if(conGeom) { + QVector points = connector->getPoints(); + int count = points.size(); + double* xArray = new double[count-2]; + double* yArray = new double[count-2]; + for(int i=1; igetModelFileInfo().filePath().toStdString().c_str()); + } + this->setSaved(true); + return; + } + // Backup old save file before saving (if old file exists) if(saveAsFlag == ExistingFile && gpConfig->getBoolSetting(cfg::autobackup)) { diff --git a/HopsanGUI/Widgets/ModelWidget.h b/HopsanGUI/Widgets/ModelWidget.h index 887983a6dc..4dc8d2d687 100644 --- a/HopsanGUI/Widgets/ModelWidget.h +++ b/HopsanGUI/Widgets/ModelWidget.h @@ -42,6 +42,8 @@ #include #include +#include + //Hopsan includes #include "common.h" @@ -56,6 +58,7 @@ class AnimationWidget; class SimulationThreadHandler; class GUIMessageHandler; class LogDataHandler2; +class ssdHandle; #include "CoreAccess.h" #include "RemoteCoreAccess.h" @@ -70,6 +73,7 @@ class ModelWidget : public QWidget enum ModelType { HopsanModel = 0x0, DcpModel = 0x1, + SsdModel = 0x2, }; ModelWidget(ModelHandler *pModelHandler, CentralTabWidget *pParentTabWidget = nullptr); @@ -123,6 +127,8 @@ class ModelWidget : public QWidget GraphicsView *mpGraphicsView; AnimationWidget *mpAnimationWidget; + void setSsdHandle(ssdHandle* ssd, sspHandle* ssp = nullptr); + public slots: void setTopLevelSimulationTime(const QString startTime, const QString timeStep, const QString stopTime, UndoStatusEnumT undoSettings=Undo); bool simulate_nonblocking(); @@ -195,6 +201,9 @@ private slots: double mSimulationProgress; QVector mRemoteResultVariables; QMutex mSimulateMutex; + + ssdHandle *mSsd; + sspHandle *mSsp; }; diff --git a/HopsanGUI/Widgets/SSMEditorWidget.cpp b/HopsanGUI/Widgets/SSMEditorWidget.cpp new file mode 100644 index 0000000000..acb305f1a7 --- /dev/null +++ b/HopsanGUI/Widgets/SSMEditorWidget.cpp @@ -0,0 +1,147 @@ +/*----------------------------------------------------------------------------- + + Copyright 2017 Hopsan Group + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + + The full license is available in the file GPLv3. + For details about the 'Hopsan Group' or information about Authors and + Contributors see the HOPSANGROUP and AUTHORS files that are located in + the Hopsan source code root directory. + +-----------------------------------------------------------------------------*/ + +//! +//! @file SSMEditorWidget.cpp +//! @brief A widget for editing SSM parameter mappings +//! @author Robert Braun +//! +//$Id$ + +#include "SSMEditorWidget.h" +#include +#include +#include +#include +#include + +#include "ssp4c_ssm_parameter_mapping.h" +#include "ssp4c_ssm_mapping_entry.h" + +SSMEditorWidget::SSMEditorWidget(ssmParameterMappingHandle *pSsm, QString fileName, QWidget *parent) + : QWidget(parent), mpSsm(pSsm), mFileName(fileName) +{ + // Create table widget with columns for Source and Target + mpTable = new QTableWidget(this); + mpTable->setColumnCount(2); + mpTable->setHorizontalHeaderLabels(QStringList() << "Source" << "Target"); + mpTable->horizontalHeader()->setStretchLastSection(true); + mpTable->setSelectionBehavior(QAbstractItemView::SelectRows); + mpTable->setSelectionMode(QAbstractItemView::SingleSelection); + + // Create buttons + mpAddButton = new QPushButton("Add Mapping", this); + mpRemoveButton = new QPushButton("Remove Mapping", this); + + // Connect button signals + connect(mpAddButton, SIGNAL(clicked()), this, SLOT(addMapping())); + connect(mpRemoveButton, SIGNAL(clicked()), this, SLOT(removeMapping())); + + // Create layout + QVBoxLayout *pMainLayout = new QVBoxLayout(this); + pMainLayout->addWidget(mpTable); + + QHBoxLayout *pButtonLayout = new QHBoxLayout(); + pButtonLayout->addWidget(mpAddButton); + pButtonLayout->addWidget(mpRemoveButton); + pButtonLayout->addStretch(); + + pMainLayout->addLayout(pButtonLayout); + + // Load the SSM data + loadSSMData(); +} + +SSMEditorWidget::~SSMEditorWidget() +{ +} + +void SSMEditorWidget::loadSSMData() +{ + if (!mpSsm) + return; + + // Clear existing rows + mpTable->setRowCount(0); + + // Get number of mapping entries + int entryCount = ssp4c_ssm_parameterMapping_getNumberOfMappingEntries(mpSsm); + + // Add each mapping entry as a table row + for (int i = 0; i < entryCount; ++i) { + ssmParameterMappingEntryHandle *pEntry = ssp4c_ssm_parameterMapping_getMappingEntryByIndex(mpSsm, i); + if (!pEntry) + continue; + + // Insert a new row + int row = mpTable->rowCount(); + mpTable->insertRow(row); + + // Get mapping data + QString source = QString::fromUtf8(ssp4c_ssm_mappingEntry_getSource(pEntry)); + QString target = QString::fromUtf8(ssp4c_ssm_mappingEntry_getTarget(pEntry)); + + // Add items to table + QTableWidgetItem *pSourceItem = new QTableWidgetItem(source); + QTableWidgetItem *pTargetItem = new QTableWidgetItem(target); + + // Add items to row + mpTable->setItem(row, 0, pSourceItem); + mpTable->setItem(row, 1, pTargetItem); + } + + // Resize columns to content + mpTable->resizeColumnsToContents(); +} + +void SSMEditorWidget::addMapping() +{ + // Insert a new row at the end + int row = mpTable->rowCount(); + mpTable->insertRow(row); + + // Create editable items + QTableWidgetItem *pSourceItem = new QTableWidgetItem(""); + QTableWidgetItem *pTargetItem = new QTableWidgetItem(""); + + // Add items to row + mpTable->setItem(row, 0, pSourceItem); + mpTable->setItem(row, 1, pTargetItem); + + // Resize columns + mpTable->resizeColumnsToContents(); + + // Note: Actual SSM data modification would happen on save +} + +void SSMEditorWidget::removeMapping() +{ + // Remove currently selected row + int currentRow = mpTable->currentRow(); + if (currentRow >= 0) { + mpTable->removeRow(currentRow); + } + + // Note: Actual SSM data modification would happen on save +} diff --git a/HopsanGUI/Widgets/SSMEditorWidget.h b/HopsanGUI/Widgets/SSMEditorWidget.h new file mode 100644 index 0000000000..b6bae490e5 --- /dev/null +++ b/HopsanGUI/Widgets/SSMEditorWidget.h @@ -0,0 +1,65 @@ +/*----------------------------------------------------------------------------- + + Copyright 2017 Hopsan Group + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + + The full license is available in the file GPLv3. + For details about the 'Hopsan Group' or information about Authors and + Contributors see the HOPSANGROUP and AUTHORS files that are located in + the Hopsan source code root directory. + +-----------------------------------------------------------------------------*/ + +//! +//! @file SSMEditorWidget.h +//! @brief A widget for editing SSM parameter mappings +//! @author Robert Braun +//! +//$Id$ + +#ifndef SSMEDITORGWIDGET_H +#define SSMEDITORGWIDGET_H + +#include +#include +#include + +// Forward declarations +class ssmParameterMappingHandle; +class QPushButton; + +class SSMEditorWidget : public QWidget +{ + Q_OBJECT + +public: + SSMEditorWidget(ssmParameterMappingHandle *pSsm, QString fileName, QWidget *parent = nullptr); + ~SSMEditorWidget(); + + void loadSSMData(); + +protected slots: + void addMapping(); + void removeMapping(); + +private: + ssmParameterMappingHandle *mpSsm; + QString mFileName; + QTableWidget *mpTable; + QPushButton *mpAddButton; + QPushButton *mpRemoveButton; +}; + +#endif // SSMEDITORGWIDGET_H diff --git a/HopsanGUI/Widgets/SSPWidget.cpp b/HopsanGUI/Widgets/SSPWidget.cpp new file mode 100644 index 0000000000..52f0eae363 --- /dev/null +++ b/HopsanGUI/Widgets/SSPWidget.cpp @@ -0,0 +1,559 @@ +/*----------------------------------------------------------------------------- + + Copyright 2017 Hopsan Group + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + + The full license is available in the file GPLv3. + For details about the 'Hopsan Group' or information about Authors and + Contributors see the HOPSANGROUP and AUTHORS files that are located in + the Hopsan source code root directory. + +-----------------------------------------------------------------------------*/ + +//! +//! @file SSPWidget.cpp +//! @brief A widget for exploring SSPs +//! @author Robert Braun +//! +//$Id$ + +#include "SSPWidget.h" +#include "SSVEditorWidget.h" +#include "SSMEditorWidget.h" +#include "Widgets/ProjectTabWidget.h" +#include "GUIObjects/GUIContainerObject.h" +#include "GUIObjects/GUIModelObject.h" +#include "GUIObjects/GUIComponent.h" +#include "GUIPort.h" +#include "ModelHandler.h" +#include "common.h" +#include +#include +#include +#include +#include +#include +#include + +#include "global.h" +#include "ssp4c.h" +#include "ssp4c_ssd.h" +#include "ssp4c_ssd_system.h" +#include "ssp4c_ssd_component.h" +#include "ssp4c_ssd_connection.h" +#include "ssp4c_ssd_connection_geometry.h" +#include "ssp4c_ssd_connector.h" +#include "ssp4c_ssd_connector_geometry.h" +#include "ssp4c_ssd_element_geometry.h" +#include "ssp4c_ssv_parameter_set.h" +#include "ssp4c_ssv_parameter.h" +#include "ssp4c_ssm_parameter_mapping.h" +#include "ssp4c_ssm_mapping_entry.h" + +SSPWidget::SSPWidget(QWidget *pParent) +{ + this->setObjectName("SSPWidget"); + this->setWindowTitle("SSP Explorer"); + + mpTree = new SSPTreeWidget(this); + + QVBoxLayout *pLayout = new QVBoxLayout(this);; + pLayout->addWidget(mpTree); +} + +void SSPWidget::addSSP(QFileInfo path) +{ + sspHandle *ssp = ssp4c_loadSsp(path.absoluteFilePath().toStdString().c_str()); + + QFont boldFont = qApp->font(); + boldFont.setBold(true); + + QTreeWidgetItem *pSspItem = new QTreeWidgetItem(SSPTreeWidget::SSPItem); + pSspItem->setFont(0,boldFont); + pSspItem->setIcon(0, QIcon(QString(ICONPATH) + "svg/Hopsan-SSP.svg")); + pSspItem->setText(0,path.fileName()); + mpTree->addTopLevelItem(pSspItem); + + itemToSspMap.insert(pSspItem, ssp); + mSspFileMap.insert(ssp, path); + + connect(mpTree, SIGNAL(itemDoubleClicked(QTreeWidgetItem*,int)), this, SLOT(openSSDModel(QTreeWidgetItem*,int))); + + qDebug() << "SSP unzipped path: " << ssp4c_getUnzippedLocation(ssp); + int ssdCount = ssp4c_getNumberOfSsds(ssp); + for(int i=0; isetIcon(0, QIcon(QString(ICONPATH) + "svg/Hopsan-SSD.svg")); + QString itemName = ssp4c_ssd_getFileName(ssd); + QString name = ssp4c_ssd_getName(ssd); + if(!name.isEmpty()) { + itemName.append(" (\""); + itemName.append(name); + itemName.append("\")"); + } + pSsdItem->setText(0,itemName); + pSspItem->addChild(pSsdItem); + + itemToSspMap.insert(pSsdItem, ssp); + itemToSsdMap.insert(pSsdItem, ssd); + + auto rootSystem = ssp4c_ssd_getRootSystem(ssd); + if(rootSystem != nullptr) { + QTreeWidgetItem *pSystemItem = new QTreeWidgetItem(SSPTreeWidget::SystemItem); + pSystemItem->setText(0, QString(ssp4c_ssd_system_getName(rootSystem))); + pSsdItem->addChild(pSystemItem); + itemToSspMap.insert(pSystemItem, ssp); + itemToSsdMap.insert(pSystemItem, ssd); + itemToSystemMap.insert(pSystemItem, rootSystem); + int componentCount = ssp4c_ssd_system_getNumberOfComponents(rootSystem); + for(int i=0; isetText(0, QString(ssp4c_ssd_component_getName(comp))); + pCompItem->setIcon(0, QIcon(QString(ICONPATH) + "svg/Hopsan-FMI.svg")); + pSystemItem->addChild(pCompItem); + } + } + + + } + + QTreeWidgetItem *pResourcesItem = new QTreeWidgetItem(); + pResourcesItem->setText(0, "Resources"); + pSspItem->addChild(pResourcesItem); + + int ssvCount = ssp4c_getNumberOfSsvs(ssp); + qDebug() << "Found" << ssvCount << "SSVs"; + for(int i=0; isetIcon(0, QIcon(QString(ICONPATH) + "svg/Hopsan-SSV.svg")); + QString itemName = ssp4c_ssv_parameterSet_getFileName(ssv); + QString name = ssp4c_ssv_parameterSet_getName(ssv); + if(!name.isEmpty()) { + itemName.append(" (\""); + itemName.append(name); + itemName.append("\")"); + } + pSsvItem->setText(0,itemName); + pResourcesItem->addChild(pSsvItem); + + itemToSspMap.insert(pSsvItem, ssp); + itemToSsvMap.insert(pSsvItem, ssv); + } + + int ssmCount = ssp4c_getNumberOfSsms(ssp); + qDebug() << "Found" << ssmCount << "SSMs"; + for(int i=0; isetIcon(0, QIcon(QString(ICONPATH) + "svg/Hopsan-SSM.svg")); + pSsmItem->setText(0,QString(ssp4c_ssm_parameterMapping_getFilename(ssm))); + pResourcesItem->addChild(pSsmItem); + + itemToSspMap.insert(pSsmItem, ssp); + itemToSsmMap.insert(pSsmItem, ssm); + } +} + +SSPTreeWidget::SSPTreeWidget(QWidget *parent) : QTreeWidget(parent) +{ + setMouseTracking(true); + setHeaderHidden(true); + setColumnCount(1); + + setDragEnabled(true); + setAcceptDrops(true); + setDropIndicatorShown(true); + + setDragDropMode(QAbstractItemView::DragDrop); + setDefaultDropAction(Qt::CopyAction); +} + +void SSPTreeWidget::startDrag(Qt::DropActions supportedActions) +{ + QTreeWidgetItem *item = currentItem(); + if (!item) + return; + + // Allow drag of SSV and SSM items + if (item->type() != SSVItem && item->type() != SSMItem) + return; + + QTreeWidget::startDrag(Qt::CopyAction); +} + +void SSPTreeWidget::dragMoveEvent(QDragMoveEvent *event) +{ + QTreeWidgetItem *target = itemAt(event->pos()); + + static QTreeWidgetItem *lastItem = nullptr; + if (lastItem) { + lastItem->setBackground(0, Qt::NoBrush); + } + + if (!target || (target->type() != FMUItem && target->type() != SystemItem)) + { + lastItem = nullptr; + event->ignore(); + return; + } + + target->setBackground(0, QBrush(Qt::lightGray)); + lastItem = target; + event->setDropAction(Qt::CopyAction); + event->accept(); +} + +void SSPTreeWidget::dropEvent(QDropEvent *event) +{ + QTreeWidgetItem *target = itemAt(event->pos()); + + if (!target || (target->type() != FMUItem && target->type() != SystemItem)) + { + event->ignore(); + return; + } + + QTreeWidgetItem *source = currentItem(); + + if (!source || (source->type() != SSVItem && source->type() != SSMItem)) + { + event->ignore(); + return; + } + + // Create a copy of item and add it to the target + QTreeWidgetItem *copy = new QTreeWidgetItem(source->type()); + copy->setText(0, source->text(0)); + copy->setTextColor(0, source->textColor(0)); // Preserve text color + //! @todo We must also copy the mapping between SSV/SSM item and SSV/SSM file + + target->addChild(copy); + target->setExpanded(true); + + event->acceptProposedAction(); +} + +void SSPTreeWidget::contextMenuEvent(QContextMenuEvent *event) +{ + QTreeWidgetItem *item = itemAt(event->pos()); + if (!item) { + return; + } + + QMenu menu(this); + + if(item->type() == SSPItem) { + menu.addAction("Save SSP", [this, item]() { + auto p = qobject_cast(parentWidget()); + if(p) { + p->saveSspModel(item); + } + }); + } + + if((item->type() == SSVItem || item->type() == SSMItem) && item->parent()->type() != SSPItem) { + menu.addAction("Remove from SSD", [this, item]() { + QTreeWidgetItem *parent = item->parent(); + if(parent) { + parent->removeChild(item); + // @todo Also remove the mapping between SSV/SSM item and SSV/SSM file + } + }); + } + + menu.exec(event->globalPos()); +} + +void SSPWidget::openSSDModel(QTreeWidgetItem *item, int) +{ + if (!item) return; + + QMap portToConnectorMap; + + // Check if this is an SSD item + if (item->type() == SSPTreeWidget::SSDItem || item->type() == SSPTreeWidget::SystemItem) { + sspHandle *ssp; + ssdHandle *ssd; + ssdSystemHandle *system; + if(!itemToSsdMap.contains(item)) { + return; + } + + ssp = itemToSspMap[item]; + ssd = itemToSsdMap[item]; + system = ssp4c_ssd_getRootSystem(ssd); + + //! @todo Check so that SSD is not already open somehow + + auto pModel = gpModelHandler->addNewModel(ssp4c_ssd_getName(ssd)); + pModel->setModelType(ModelWidget::SsdModel); + pModel->setSsdHandle(ssd, ssp); + + int componentCount = ssp4c_ssd_system_getNumberOfComponents(system); + for(int i=0; igetCurrentTopLevelSystem(); + if(pSystem) { + QPointF pos = pSystem->getGraphicsViewport().mCenter; + ModelObject *pFmuComponent = pSystem->addModelObject("FMIWrapper", pos, 0, Deselected, UseDefault, Undo, Resizable); + + if(pFmuComponent) { + QString unzippedLocation = ssp4c_getUnzippedLocation(ssp); + QString source = ssp4c_ssd_component_getSource(comp); + pFmuComponent->setParameterValue("path", unzippedLocation+"/"+source); + pSystem->renameModelObject(pFmuComponent->getName(), ssp4c_ssd_component_getName(comp)); + ResizableComponent *pRC = qobject_cast(pFmuComponent); + pRC->setText(ssp4c_ssd_component_getName(comp)); + + // Set component size and position from ElementGeometry in SSD + ssdElementGeometryHandle *geom = nullptr; + geom = ssp4c_ssd_component_getElementGeometry(comp); + if(geom) { + double x1 = ssp4c_ssd_elementGeometry_getX1(geom) + 2500; //2500,2500 is center of view + double x2 = ssp4c_ssd_elementGeometry_getX2(geom) + 2500; + double y1 = ssp4c_ssd_elementGeometry_getY1(geom) + 2500; + double y2 = ssp4c_ssd_elementGeometry_getY2(geom) + 2500; + double width = x2 - x1; + double height = y2 - y1; + if(width < 1) { + width = 100; + } + if(height < 1) { + height = 100; + } + double rotation = ssp4c_ssd_elementGeometry_getRotation(geom); + + // Set position and size + pFmuComponent->setPos(x1, y1); + if(width > 0 && height > 0) { + pRC->setComponentSize(width, height); + } + if(rotation != 0) { + pFmuComponent->setRotation(rotation); + } + } + } + + int connectorsCount = ssp4c_ssd_component_getNumberOfConnectors(comp); + QStringList visiblePorts; + for(int i=0; igetPort(ssp4c_ssd_connector_getName(con)); + if(port != nullptr) { + qDebug() << "x = " << x; + qDebug() << "y = " << y; + port->getPortAppearance()->x = x; + port->getPortAppearance()->y = y; + pFmuComponent->createRefreshExternalPort(ssp4c_ssd_connector_getName(con)); + portToConnectorMap.insert(port, con); + } + } + + + qDebug() << " connector: " << ssp4c_ssd_connector_getName(con); + } + for(const auto &port : pFmuComponent->getPortListPtrs()) { + port->setVisible(visiblePorts.contains(port->getName())); + } + } + } + + int connetionCount = ssp4c_ssd_system_getNumberOfConnections(system); + for(int i=0; igetCurrentTopLevelSystem(); + if(pSystem) { + ModelObject *pStartComponent = pSystem->getModelObject(ssp4c_ssd_connection_getStartElement(con)); + ModelObject *pEndComponent = pSystem->getModelObject(ssp4c_ssd_connection_getEndElement(con)); + if(pStartComponent != nullptr && pEndComponent != nullptr) { + qDebug() << "Found components!"; + QString startPortName = ssp4c_ssd_connection_getStartConnector(con); + startPortName.replace(".", "_"); + QString endPortName = ssp4c_ssd_connection_getEndConnector(con); + endPortName.replace(".", "_"); + Port *pStartPort = pStartComponent->getPort(startPortName); + Port *pEndPort = pEndComponent->getPort(endPortName); + if(pStartPort != nullptr && pEndPort != nullptr) { + qDebug() << "Found ports!"; + SystemObject *sysObj = gpModelHandler->getCurrentViewContainerObject(); + Connector *pCon = sysObj->createConnector(pStartPort, pEndPort, NoUndo); + if(pCon != nullptr) { + qDebug() << "Created connector!"; + QVector pointVector; + QStringList geometryList; + + //Start port + auto startPort = pCon->getStartPort(); + pointVector.push_back(startPort->mapToScene(startPort->boundingRect().center())); + + //Middle points + if(conGeom != nullptr) { + int count = 0; + double *pointsX = ssp4c_ssd_connectionGeometry_getPointsX(conGeom, &count); + double *pointsY = ssp4c_ssd_connectionGeometry_getPointsY(conGeom, &count); + for(int i=0; igetEndPort(); + pointVector.push_back(endPort->mapToScene(endPort->boundingRect().center())); + + pCon->setPointsAndGeometries(pointVector, geometryList); + pCon->refreshConnectorAppearance(); + + } + } + } + } + } + } + // Check if this is an SSV item + else if (item->type() == SSPTreeWidget::SSVItem) { + openSSVEditor(item, 0); + } + // Check if this is an SSM item + else if (item->type() == SSPTreeWidget::SSMItem) { + openSSMEditor(item, 0); + } +} + +void SSPWidget::saveSspModel(QTreeWidgetItem *item) +{ + if(!item || item->type() != SSPTreeWidget::SSPItem) { + return; + } + + if(!itemToSspMap.contains(item)) { + qDebug() << "SSP save: no associated ssp handle"; + return; + } + + sspHandle *ssp = itemToSspMap[item]; + QFileInfo outFile = mSspFileMap.value(ssp); + + if(outFile.filePath().isEmpty()) { + QString saveFile = QFileDialog::getSaveFileName(this, tr("Save SSP"), QString(), tr("SSP Files (*.ssp);;All Files (*)")); + if(saveFile.isEmpty()) { + return; // canceled + } + outFile.setFile(saveFile); + } + + bool success = (ssp4c_saveSsp(ssp, outFile.absoluteFilePath().toStdString().c_str()) == 0); + if(success) { + qDebug() << "SSP saved:" << outFile.absoluteFilePath(); + } + else { + qDebug() << "SSP save failed:" << outFile.absoluteFilePath(); + } +} + +void SSPWidget::openSSVEditor(QTreeWidgetItem *item, int) +{ + if (!item || !itemToSsvMap.contains(item)) { + return; + } + + ssvParameterSetHandle *ssv = itemToSsvMap[item]; + QString fileName = QString::fromUtf8(ssp4c_ssv_parameterSet_getFileName(ssv)); + + // Create the SSV editor widget + SSVEditorWidget *pEditor = new SSVEditorWidget(ssv, fileName, gpCentralTabWidget); + + // Add to the central tab widget + if (gpCentralTabWidget) { + gpCentralTabWidget->addTab(pEditor, fileName); + gpCentralTabWidget->setCurrentWidget(pEditor); + } +} + +void SSPWidget::openSSMEditor(QTreeWidgetItem *item, int) +{ + if (!item || !itemToSsmMap.contains(item)) { + return; + } + + ssmParameterMappingHandle *ssm = itemToSsmMap[item]; + QString fileName = QString::fromUtf8(ssp4c_ssm_parameterMapping_getFilename(ssm)); + + // Create the SSM editor widget + SSMEditorWidget *pEditor = new SSMEditorWidget(ssm, fileName, gpCentralTabWidget); + + // Add to the central tab widget + if (gpCentralTabWidget) { + gpCentralTabWidget->addTab(pEditor, fileName); + gpCentralTabWidget->setCurrentWidget(pEditor); + } +} diff --git a/HopsanGUI/Widgets/SSPWidget.h b/HopsanGUI/Widgets/SSPWidget.h new file mode 100644 index 0000000000..c1cbf663af --- /dev/null +++ b/HopsanGUI/Widgets/SSPWidget.h @@ -0,0 +1,70 @@ +#include +#include +#include + +#ifndef SSPWIDGET_H +#define SSPWIDGET_H + +#include + +//Forward declarations +class sspHandle; +class ssdHandle; +class ssvParameterSetHandle; +class ssmParameterMappingHandle; +class ssdSystemHandle; +class SSPTreeWidget; + +class SSPWidget : public QWidget +{ + Q_OBJECT +public: + SSPWidget(QWidget *pParent=0); + + void addSSP(QFileInfo path); + +public slots: + void saveSspModel(QTreeWidgetItem *item); + +protected slots: + void openSSDModel(QTreeWidgetItem*item, int); + void openSSVEditor(QTreeWidgetItem*item, int); + void openSSMEditor(QTreeWidgetItem*item, int); + +private: + SSPTreeWidget *mpTree; + + QMap itemToSspMap; + QMap itemToSsdMap; + QMap itemToSystemMap; + QMap itemToSsvMap; + QMap itemToSsmMap; + QMap mSspFileMap; +}; + +class SSPTreeWidget : public QTreeWidget +{ + Q_OBJECT + +public: + explicit SSPTreeWidget(QWidget *parent = nullptr); + + enum ItemType { + SSPItem, + SSDItem = QTreeWidgetItem::UserType + 1, + SystemItem, + FMUItem, + SSVItem, + SSMItem + }; + +protected: + void startDrag(Qt::DropActions supportedActions) override; + void dragMoveEvent(QDragMoveEvent *event) override; + void dropEvent(QDropEvent *event) override; + void contextMenuEvent(QContextMenuEvent *event) override; +}; + + + +#endif // SSPWIDGET_H diff --git a/HopsanGUI/Widgets/SSVEditorWidget.cpp b/HopsanGUI/Widgets/SSVEditorWidget.cpp new file mode 100644 index 0000000000..84feedbf22 --- /dev/null +++ b/HopsanGUI/Widgets/SSVEditorWidget.cpp @@ -0,0 +1,183 @@ +/*----------------------------------------------------------------------------- + + Copyright 2017 Hopsan Group + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + + The full license is available in the file GPLv3. + For details about the 'Hopsan Group' or information about Authors and + Contributors see the HOPSANGROUP and AUTHORS files that are located in + the Hopsan source code root directory. + +-----------------------------------------------------------------------------*/ + +//! +//! @file SSVEditorWidget.cpp +//! @brief A widget for editing SSV parameter sets +//! @author Robert Braun +//! +//$Id$ + +#include "SSVEditorWidget.h" +#include +#include +#include +#include +#include + +#include "ssp4c.h" +#include "ssp4c_ssv_parameter_set.h" +#include "ssp4c_ssv_parameter.h" + +SSVEditorWidget::SSVEditorWidget(ssvParameterSetHandle *pSsv, QString fileName, QWidget *parent) + : QWidget(parent), mpSsv(pSsv), mFileName(fileName) +{ + // Create table widget with columns for Name, Unit, and Value + mpTable = new QTableWidget(this); + mpTable->setColumnCount(3); + mpTable->setHorizontalHeaderLabels(QStringList() << "Name" << "Unit" << "Value"); + mpTable->horizontalHeader()->setStretchLastSection(true); + mpTable->setSelectionBehavior(QAbstractItemView::SelectRows); + mpTable->setSelectionMode(QAbstractItemView::SingleSelection); + + // Create buttons + mpAddButton = new QPushButton("Add Parameter", this); + mpRemoveButton = new QPushButton("Remove Parameter", this); + + // Connect button signals + connect(mpAddButton, SIGNAL(clicked()), this, SLOT(addParameter())); + connect(mpRemoveButton, SIGNAL(clicked()), this, SLOT(removeParameter())); + + // Create layout + QVBoxLayout *pMainLayout = new QVBoxLayout(this); + pMainLayout->addWidget(mpTable); + + QHBoxLayout *pButtonLayout = new QHBoxLayout(); + pButtonLayout->addWidget(mpAddButton); + pButtonLayout->addWidget(mpRemoveButton); + pButtonLayout->addStretch(); + + pMainLayout->addLayout(pButtonLayout); + + // Load the SSV data + loadSSVData(); +} + +SSVEditorWidget::~SSVEditorWidget() +{ +} + +void SSVEditorWidget::loadSSVData() +{ + if (!mpSsv) + return; + + // Clear existing rows + mpTable->setRowCount(0); + + // Get number of parameters + int paramCount = ssp4c_ssv_parameterSet_getNumberOfParameters(mpSsv); + + // Add each parameter as a table row + for (int i = 0; i < paramCount; ++i) { + ssvParameterHandle *pParam = ssp4c_ssv_parameterSet_getParameterByIndex(mpSsv, i); + if (!pParam) + continue; + + // Insert a new row + int row = mpTable->rowCount(); + mpTable->insertRow(row); + + // Get parameter data + QString name = QString::fromUtf8(ssp4c_ssv_parameter_getName(pParam)); + QString unit = QString::fromUtf8(ssp4c_ssv_parameter_getUnit(pParam)); + QString value; + + sspDataType type = ssp4c_ssv_parameter_getDatatype(pParam); + if (type == sspDataTypeString) { + value = QString::fromUtf8(ssp4c_ssv_parameter_getStringValue(pParam)); + } else if (type == sspDataTypeReal || type == sspDataTypeFloat64 || type == sspDataTypeFloat32) { + value = QString::number(ssp4c_ssv_parameter_getRealValue(pParam)); + } else if (type == sspDataTypeInteger || + type == sspDataTypeInt8 || type == sspDataTypeUInt8 || + type == sspDataTypeInt16 || type == sspDataTypeUInt16 || + type == sspDataTypeInt32 || type == sspDataTypeUInt32 || + type == sspDataTypeInt64 || type == sspDataTypeUInt64) { + // For integer types, try to get as double and convert + value = QString::number((int)ssp4c_ssv_parameter_getRealValue(pParam)); + } else if (type == sspDataTypeBoolean) { + // For boolean, show as "true" or "false" based on the value + double boolValue = ssp4c_ssv_parameter_getRealValue(pParam); + value = (boolValue != 0.0) ? "true" : "false"; + } else { + // For other types (Enumeration, Binary, etc.), try to get string representation + value = QString::fromUtf8(ssp4c_ssv_parameter_getStringValue(pParam)); + } + + // Add items to table + QTableWidgetItem *pNameItem = new QTableWidgetItem(name); + QTableWidgetItem *pUnitItem = new QTableWidgetItem(unit); + QTableWidgetItem *pValueItem = new QTableWidgetItem(value); + + // Add items to row + mpTable->setItem(row, 0, pNameItem); + mpTable->setItem(row, 1, pUnitItem); + mpTable->setItem(row, 2, pValueItem); + + // Make name and unit read-only, but allow value editing + pNameItem->setFlags(pNameItem->flags() & ~Qt::ItemIsEditable); + pUnitItem->setFlags(pUnitItem->flags() & ~Qt::ItemIsEditable); + } + + // Resize columns to content + mpTable->resizeColumnsToContents(); +} + + +void SSVEditorWidget::addParameter() +{ + // Insert a new row at the end + int row = mpTable->rowCount(); + mpTable->insertRow(row); + + // Create editable items + QTableWidgetItem *pNameItem = new QTableWidgetItem("New_Parameter"); + QTableWidgetItem *pUnitItem = new QTableWidgetItem(""); + QTableWidgetItem *pValueItem = new QTableWidgetItem("0"); + + // Make name and unit read-only + pNameItem->setFlags(pNameItem->flags() & ~Qt::ItemIsEditable); + pUnitItem->setFlags(pUnitItem->flags() & ~Qt::ItemIsEditable); + + // Add items to row + mpTable->setItem(row, 0, pNameItem); + mpTable->setItem(row, 1, pUnitItem); + mpTable->setItem(row, 2, pValueItem); + + // Resize columns + mpTable->resizeColumnsToContents(); + + // Note: Actual SSV data modification would happen on save +} + +void SSVEditorWidget::removeParameter() +{ + // Remove currently selected row + int currentRow = mpTable->currentRow(); + if (currentRow >= 0) { + mpTable->removeRow(currentRow); + } + + // Note: Actual SSV data modification would happen on save +} diff --git a/HopsanGUI/Widgets/SSVEditorWidget.h b/HopsanGUI/Widgets/SSVEditorWidget.h new file mode 100644 index 0000000000..6b3b77c8d9 --- /dev/null +++ b/HopsanGUI/Widgets/SSVEditorWidget.h @@ -0,0 +1,65 @@ +/*----------------------------------------------------------------------------- + + Copyright 2017 Hopsan Group + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + + The full license is available in the file GPLv3. + For details about the 'Hopsan Group' or information about Authors and + Contributors see the HOPSANGROUP and AUTHORS files that are located in + the Hopsan source code root directory. + +-----------------------------------------------------------------------------*/ + +//! +//! @file SSVEditorWidget.h +//! @brief A widget for editing SSV parameter sets +//! @author Robert Braun +//! +//$Id$ + +#ifndef SSVEDITORWIDGET_H +#define SSVEDITORWIDGET_H + +#include +#include +#include + +// Forward declarations +class ssvParameterSetHandle; +class QPushButton; + +class SSVEditorWidget : public QWidget +{ + Q_OBJECT + +public: + SSVEditorWidget(ssvParameterSetHandle *pSsv, QString fileName, QWidget *parent = nullptr); + ~SSVEditorWidget(); + + void loadSSVData(); + +protected slots: + void addParameter(); + void removeParameter(); + +private: + ssvParameterSetHandle *mpSsv; + QString mFileName; + QTableWidget *mpTable; + QPushButton *mpAddButton; + QPushButton *mpRemoveButton; +}; + +#endif // SSVEDITORWIDGET_H diff --git a/HopsanGUI/Widgets/WelcomeWidget.cpp b/HopsanGUI/Widgets/WelcomeWidget.cpp index 1fe1649295..1429e7c6ab 100644 --- a/HopsanGUI/Widgets/WelcomeWidget.cpp +++ b/HopsanGUI/Widgets/WelcomeWidget.cpp @@ -580,7 +580,13 @@ void WelcomeWidget::mousePressEvent(QMouseEvent *event) //! @brief Opens selected recent model from the list and closes the welcome dialog. void WelcomeWidget::openRecentModel() { - gpModelHandler->loadModel(mRecentModelList.at(mpRecentList->currentIndex().row())); + QString path = mRecentModelList.at(mpRecentList->currentIndex().row()); + if(QFileInfo(path).suffix() == "ssp") { + gpModelHandler->loadSsp(path); + } + else { + gpModelHandler->loadModel(path); + } } diff --git a/HopsanGUI/common.h b/HopsanGUI/common.h index 52c2081747..0bcedb7637 100644 --- a/HopsanGUI/common.h +++ b/HopsanGUI/common.h @@ -83,6 +83,7 @@ enum GraphicsTypeEnumT {UserGraphics, ISOGraphics, NoGraphics}; enum ConnectorGeometryEnumT {Vertical, Horizontal, Diagonal}; enum RenameRestrictionEnumT {Unrestricted, CoreRenameOnly}; enum UndoStatusEnumT {NoUndo, Undo}; +enum ResizbleEnumT {NotResizable, Resizable}; enum SaveTargetEnumT {ExistingFile, NewFile}; enum SaveContentsEnumT {FullModel, ParametersOnly}; enum ConnectorStyleEnumT {PowerConnectorStyle, SignalConnectorStyle, BrokenConnectorStyle, UndefinedConnectorStyle}; diff --git a/HopsanGUI/global.h b/HopsanGUI/global.h index ed3be14611..bf21da51eb 100644 --- a/HopsanGUI/global.h +++ b/HopsanGUI/global.h @@ -61,6 +61,7 @@ class OptionsDialog; class QGridLayout; class FindWidget; class PlotWidget2; +class SSPWidget; // Global pointer to the main window and QWidget cast version extern MainWindow* gpMainWindow; @@ -80,6 +81,7 @@ extern ModelHandler *gpModelHandler; extern PlotWidget2 *gpPlotWidget; extern CentralTabWidget *gpCentralTabWidget; extern SystemParametersWidget *gpSystemParametersWidget; +extern SSPWidget *gpSSPWidget; extern UndoWidget *gpUndoWidget; extern LibraryHandler *gpLibraryHandler; extern HelpPopUpWidget *gpHelpPopupWidget; diff --git a/HopsanGUI/graphics/uiicons/svg/Hopsan-FMI.svg b/HopsanGUI/graphics/uiicons/svg/Hopsan-FMI.svg new file mode 100644 index 0000000000..6688a5eae8 --- /dev/null +++ b/HopsanGUI/graphics/uiicons/svg/Hopsan-FMI.svg @@ -0,0 +1,124 @@ + +image/svg+xml \ No newline at end of file diff --git a/HopsanGUI/graphics/uiicons/svg/Hopsan-SSD.svg b/HopsanGUI/graphics/uiicons/svg/Hopsan-SSD.svg new file mode 100644 index 0000000000..9edc1c5d3c --- /dev/null +++ b/HopsanGUI/graphics/uiicons/svg/Hopsan-SSD.svg @@ -0,0 +1,68 @@ + + + +image/svg+xml diff --git a/HopsanGUI/graphics/uiicons/svg/Hopsan-SSM.svg b/HopsanGUI/graphics/uiicons/svg/Hopsan-SSM.svg new file mode 100644 index 0000000000..c49482b0fb --- /dev/null +++ b/HopsanGUI/graphics/uiicons/svg/Hopsan-SSM.svg @@ -0,0 +1,73 @@ + + + +image/svg+xml diff --git a/HopsanGUI/graphics/uiicons/svg/Hopsan-SSP.svg b/HopsanGUI/graphics/uiicons/svg/Hopsan-SSP.svg new file mode 100644 index 0000000000..ea17b1c8f0 --- /dev/null +++ b/HopsanGUI/graphics/uiicons/svg/Hopsan-SSP.svg @@ -0,0 +1,71 @@ + + + +image/svg+xml diff --git a/HopsanGUI/graphics/uiicons/svg/Hopsan-SSV.svg b/HopsanGUI/graphics/uiicons/svg/Hopsan-SSV.svg new file mode 100644 index 0000000000..c93c9e4cfa --- /dev/null +++ b/HopsanGUI/graphics/uiicons/svg/Hopsan-SSV.svg @@ -0,0 +1,69 @@ + + + +image/svg+xml diff --git a/HopsanGenerator/templates/fmu3_model.c b/HopsanGenerator/templates/fmu3_model.c index ff24969f77..0937ba0be0 100644 --- a/HopsanGenerator/templates/fmu3_model.c +++ b/HopsanGenerator/templates/fmu3_model.c @@ -233,7 +233,7 @@ fmi3Status fmi3EnterInitializationMode(fmi3Instance instance, } if(fmu->loggingOn) { - fmu->logger(fmu->instanceEnvironment, fmi3Error, "info", "Entering initialization mode..."); + fmu->logger(fmu->instanceEnvironment, fmi3Info, "info", "Entering initialization mode..."); } fmu->pSystem->initialize(startTime, stopTime); diff --git a/componentLibraries/defaultLibrary/Connectivity/FMIWrapper.hpp b/componentLibraries/defaultLibrary/Connectivity/FMIWrapper.hpp index c6542a3b2c..bed25404d7 100644 --- a/componentLibraries/defaultLibrary/Connectivity/FMIWrapper.hpp +++ b/componentLibraries/defaultLibrary/Connectivity/FMIWrapper.hpp @@ -229,7 +229,7 @@ class FMIWrapper : public ComponentSignal fmi3InstanceHandle *fmi3_instance; HString mVisibleOutputs; double mTolerance = 1e-4; - bool mLoggingOn = false; + bool mLoggingOn = true; bool mReinstantiate = false; bool mIsInstantiated = false; @@ -446,6 +446,18 @@ class FMIWrapper : public ComponentSignal } } else if(mFmiVersion == fmiVersion2) { + //Instantiate FMU + if(!mReinstantiate) { + addDebugMessage("Calling: fmi2Instantiate"); + fmi2_instance = fmi2_instantiate(fmu, fmi2CoSimulation, FMIWrapper_fmi2Logger, calloc, free, NULL, (fmi2ComponentEnvironment*)this, fmi2False, mLoggingOn); + if(!fmi2_instance) { + stopSimulation("Failed to instantiate FMU"); + fmi2_instance = NULL; + return; + } + mIsInstantiated = true; + } + if(fmi2_defaultToleranceDefined(fmu)) { mTolerance = fmi2_getDefaultTolerance(fmu); } @@ -547,19 +559,6 @@ class FMIWrapper : public ComponentSignal mVisibleOutputs.erase(mVisibleOutputs.size()-1,1); //Remove trailing comma } addConstant("visibleOutputs", "Visible output variables (hidden)", "", mVisibleOutputs, mVisibleOutputs); - - - //Instantiate FMU - if(!mReinstantiate) { - addDebugMessage("Calling: fmi2Instantiate"); - fmi2_instance = fmi2_instantiate(fmu, fmi2CoSimulation, FMIWrapper_fmi2Logger, calloc, free, NULL, (fmi2ComponentEnvironment*)this, fmi2False, mLoggingOn); - if(!fmi2_instance) { - stopSimulation("Failed to instantiate FMU"); - fmi2_instance = NULL; - return; - } - mIsInstantiated = true; - } } else {//FMI 3 if(fmi3_defaultToleranceDefined(fmu)) { diff --git a/dependencies/dependencies.xml b/dependencies/dependencies.xml index 96c9f71ab9..7171049e1b 100644 --- a/dependencies/dependencies.xml +++ b/dependencies/dependencies.xml @@ -121,4 +121,10 @@ + + + https://github.com/robbr48/ssp4c/archive/refs/tags/v1.0.zip + + + diff --git a/dependencies/setupAll.bat b/dependencies/setupAll.bat index 0c80b68e3c..dbc78185fd 100644 --- a/dependencies/setupAll.bat +++ b/dependencies/setupAll.bat @@ -12,3 +12,4 @@ start /wait cmd /c setupLibzip.bat start /wait cmd /c setupXerces.bat REM DCPLib must come after xerces and libzip (zlib before libzip) start /wait cmd /c setupDCPLib.bat +start /wait cmd /c setupSsp4c.bat diff --git a/dependencies/setupAll.sh b/dependencies/setupAll.sh index 3653311667..66cb19dd0e 100755 --- a/dependencies/setupAll.sh +++ b/dependencies/setupAll.sh @@ -12,3 +12,4 @@ ./setupZeroMQ.sh # DCPLib must come after xerces and libzip ./setupDCPLib.sh +./setupSsp4c.sh diff --git a/dependencies/setupSsp4c.bat b/dependencies/setupSsp4c.bat new file mode 100644 index 0000000000..b287d1fcbc --- /dev/null +++ b/dependencies/setupSsp4c.bat @@ -0,0 +1,33 @@ +@ECHO OFF + +REM Bat script for building ssp4c dependency automatically + +setlocal +set basedir=%~dp0 +set name=ssp4c +set codedir=%basedir%\%name%-code +set builddir=%basedir%\%name%-build +set installdir=%basedir%\%name% + +REM Setup PATH +call setHopsanBuildPaths.bat + +REM build and install +if exist %builddir% ( + echo Removing existing build directory %builddir% + rmdir /S /Q %builddir% +) +mkdir %builddir% +pushd %builddir% + +cmake -Wno-dev -G %HOPSAN_BUILD_CMAKE_GENERATOR% -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=%installdir% -DSSP4C_BUILD_SHARED=OFF %codedir% +cmake --build . --config Debug --parallel 8 +cmake --build . --config Debug --target install +popd + +echo. +echo setupSsp4c.bat done!" +if "%HOPSAN_BUILD_SCRIPT_NOPAUSE%" == "" ( + pause +) +endlocal diff --git a/dependencies/setupSsp4c.sh b/dependencies/setupSsp4c.sh new file mode 100755 index 0000000000..4fbb3fa7d0 --- /dev/null +++ b/dependencies/setupSsp4c.sh @@ -0,0 +1,33 @@ +#!/bin/bash + +# Shell script building Hopsan dependency ssp4c + +basedir=$(pwd) + +name=ssp4c +codedir=${basedir}/${name}-code +builddir=${basedir}/${name}-build +installdir=${basedir}/${name} + + +# Download and verify +./download-dependencies.py ${name} + +# Include general settings +source setHopsanBuildPaths.sh + +# Create build dir and enter it +mkdir -p $builddir +cd $builddir + +# Generate makefiles +cmake -Wno-dev -DCMAKE_INSTALL_PREFIX=${installdir} -DCMAKE_BUILD_TYPE=Release -DSSP4C_BUILD_SHARED=OFF ${codedir} + +# Build and install +cmake --build . --parallel 8 +cmake --build . --target install + + +# Return to basedir +cd $basedir +echo "setupSsp4c.sh done!" diff --git a/dependencies/ssp4c.cmake b/dependencies/ssp4c.cmake new file mode 100644 index 0000000000..80f3b77180 --- /dev/null +++ b/dependencies/ssp4c.cmake @@ -0,0 +1,44 @@ +set(local_ssp4c_dir ${CMAKE_CURRENT_LIST_DIR}/ssp4c) + +if (EXISTS ${local_ssp4c_dir}) + + set(zlibstatic_name zlibstatic) + if (NOT WIN32) + set(zlibstatic_name z) + endif() + + set(ssp4c_dbg_ext "") + if (WIN32) + set(ssp4c_dbg_ext d) + endif() + + set(static_ssp4c ${local_ssp4c_dir}/lib/${CMAKE_STATIC_LIBRARY_PREFIX}ssp4c${CMAKE_STATIC_LIBRARY_SUFFIX}) + set(static_ssp4c_d ${local_ssp4c_dir}/lib/${CMAKE_STATIC_LIBRARY_PREFIX}ssp4c${ssp4c_dbg_ext}${CMAKE_STATIC_LIBRARY_SUFFIX}) + set(static_zlib ${local_ssp4c_dir}/lib/${CMAKE_STATIC_LIBRARY_PREFIX}${zlibstatic_name}${CMAKE_STATIC_LIBRARY_SUFFIX}) + set(static_zlib_d ${local_ssp4c_dir}/lib/${CMAKE_STATIC_LIBRARY_PREFIX}${zlibstatic_name}${ssp4c_dbg_ext}${CMAKE_STATIC_LIBRARY_SUFFIX}) + + add_library(ssp4c STATIC IMPORTED) + + set_target_properties(ssp4c PROPERTIES + IMPORTED_LOCATION ${static_ssp4c} + IMPORTED_LOCATION_DEBUG ${static_ssp4c_d} + INTERFACE_INCLUDE_DIRECTORIES ${local_ssp4c_dir}/include + INTERFACE_COMPILE_DEFINITIONS "USESSP4C;SSP4C_STATIC") + + if (EXISTS ${static_zlib}) + message(STATUS "Found static zlib in ${local_ssp4c_dir}") + add_library(ssp4c_zlib STATIC IMPORTED) + set_target_properties(ssp4c_zlib PROPERTIES + IMPORTED_LOCATION ${static_zlib} + IMPORTED_LOCATION_DEBUG ${static_zlib_d}) + target_link_libraries(ssp4c INTERFACE ssp4c_zlib) + else() + find_package(ZLIB MODULE REQUIRED) + target_link_libraries(ssp4c INTERFACE ZLIB::ZLIB) + endif() + + install(DIRECTORY ${local_ssp4c_dir} DESTINATION dependencies) + message(STATUS "Building with ssp4c support") +else() + message(WARNING "Building without ssp4c support") +endif() diff --git a/dependencies/ssp4c.pri b/dependencies/ssp4c.pri new file mode 100644 index 0000000000..815387e84f --- /dev/null +++ b/dependencies/ssp4c.pri @@ -0,0 +1,22 @@ +ssp4c_dir = $${PWD}/ssp4c + +exists($${ssp4c_dir}) { + DEFINES *= USESSP4C + DEFINES *= SSP4C_STATIC + INCLUDEPATH *= $${ssp4c_dir}/include + message(SSP includes: $${ssp4c_dir}/include) + + macx { + # Not supported + } win32 { + CONFIG(debug, debug|release) { + ssp4c_dbg_ext = + } + LIBS *= -L$${ssp4c_dir}/lib -lssp4c$${ssp4c_dbg_ext} + } else { + LIBS *= -L$${ssp4c_dir}/lib -lssp4c -lz + } + message(Found local ssp4c) +} else { + message(Compiling without ssp4c) +}