diff --git a/CHANGELOG.md b/CHANGELOG.md index ead9b901ed3..435e774d4fe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,11 +3,15 @@ All notable changes to this project will be documented in this file. ## [Unreleased] * miscellaneous + * added GUI PluginParameter type 'ComboBox' for parameters that can be requested from plugin + * added GUI PluginParameter types 'Module' and 'Gated' for parameters that can be requested from plugin * added `Show content` button to `Groupings` widget to show content of grouping as a list * added flag which python editor tab is active when serializing project * added `GateType::delete_pin_group` and `GateType::assign_pin_to_group` to enable more operations on pin groups of gate pins + * added extended gate library picker when importing a netlist * changed supported input file formats for import from hard coded list to list provided by loadable parser plugins * changed behavior of import netlist dialog, suggest only non-existing directory names and loop until an acceptable name was entered + * changed appearance and behavior of import project dialog, make sure existing hal projects don't get overwritten * bugfixes * fixed colors in Python Console when switching between color schemes * fixed pybind of `Module::get_gates` diff --git a/include/hal_core/plugin_system/plugin_parameter.h b/include/hal_core/plugin_system/plugin_parameter.h index eb79bd9b304..60d54da14cd 100644 --- a/include/hal_core/plugin_system/plugin_parameter.h +++ b/include/hal_core/plugin_system/plugin_parameter.h @@ -32,7 +32,7 @@ namespace hal class PluginParameter { public: - enum ParameterType { Absent, Boolean, Color, Dictionary, ExistingDir, Float, Integer, NewFile, PushButton, String, TabName }; + enum ParameterType { Absent, Boolean, Color, ComboBox, Dictionary, ExistingDir, Float, Gate, Integer, Module, NewFile, PushButton, String, TabName }; private: ParameterType m_type; std::string m_tagname; diff --git a/plugins/gui/include/gui/export/import_project_dialog.h b/plugins/gui/include/gui/export/import_project_dialog.h index f676a0696a5..3e2419c2a9d 100644 --- a/plugins/gui/include/gui/export/import_project_dialog.h +++ b/plugins/gui/include/gui/export/import_project_dialog.h @@ -61,14 +61,19 @@ namespace hal ImportStatus mStatus; FileSelectWidget* mZippedFile; FileSelectWidget* mTargetDirectory; + QLineEdit* mExtractProjectEdit; QDialogButtonBox* mButtonBox; - QString mExtractedProjectDir; + QString mTargetProjectName; + QString mExtractedProjectAbsolutePath; + static void deleteFilesList(QStringList files); + static void deleteFilesRecursion(QString dir); private Q_SLOTS: void handleSelectionStatusChanged(); public: ImportProjectDialog(QWidget* parent = nullptr); bool importProject(); ImportStatus status() const { return mStatus; } - QString extractedProjectDir() const { return mExtractedProjectDir; } + QString extractedProjectAbsolutePath() const { return mExtractedProjectAbsolutePath; } + static QString suggestedProjectDir(const QString& filename); }; } diff --git a/plugins/gui/include/gui/file_manager/import_netlist_dialog.h b/plugins/gui/include/gui/file_manager/import_netlist_dialog.h index b3042cfd29c..4353bc21c43 100644 --- a/plugins/gui/include/gui/file_manager/import_netlist_dialog.h +++ b/plugins/gui/include/gui/file_manager/import_netlist_dialog.h @@ -30,12 +30,13 @@ #include #include -class QComboBox; class QCheckBox; class QLineEdit; namespace hal { + class GateLibrarySelection; + class ImportNetlistDialog : public QDialog { Q_OBJECT @@ -43,18 +44,15 @@ namespace hal { Q_PROPERTY(QString saveIconStyle READ saveIconStyle WRITE setSaveIconStyle) QString mProjectdir; QLineEdit* mEditProjectdir; - QComboBox* mComboGatelib; + GateLibrarySelection* mGatelibSelection; QCheckBox* mCheckMoveNetlist; QCheckBox* mCheckCopyGatelib; - QStringList mGateLibraryPath; - QMap mGateLibraryMap; QString mSaveIconPath; QString mSaveIconStyle; private Q_SLOTS: - void handleGateLibraryPathChanged(const QString& txt); + void handleGatelibSelected(bool singleFile); void handleFileDialogTriggered(); - void setSuggestedProjectDir(const QString& filename); public: ImportNetlistDialog(const QString& filename, QWidget* parent = nullptr); QString projectDirectory() const; diff --git a/plugins/gui/include/gui/gatelibrary_management/gatelibrary_selection.h b/plugins/gui/include/gui/gatelibrary_management/gatelibrary_selection.h new file mode 100644 index 00000000000..a16c2d0f422 --- /dev/null +++ b/plugins/gui/include/gui/gatelibrary_management/gatelibrary_selection.h @@ -0,0 +1,95 @@ +// MIT License +// +// Copyright (c) 2019 Ruhr University Bochum, Chair for Embedded Security. All Rights reserved. +// Copyright (c) 2019 Marc Fyrbiak, Sebastian Wallat, Max Hoffmann ("ORIGINAL AUTHORS"). All rights reserved. +// Copyright (c) 2021 Max Planck Institute for Security and Privacy. All Rights reserved. +// Copyright (c) 2021 Jörn Langheinrich, Julian Speith, Nils Albartus, René Walendy, Simon Klix ("ORIGINAL AUTHORS"). All Rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +#pragma once + +#include +#include +#include +#include + +class QComboBox; +class QPushButton; +class QCheckBox; + +namespace hal { + class GateLibrarySelectionEntry + { + QString mName; + QString mPath; + int mCount; + public: + GateLibrarySelectionEntry(const QString& name_, const QString& path_, int cnt) + : mName(name_), mPath(path_), mCount(cnt) {;} + QVariant data(int column, bool fullPath) const; + QString name() const {return mName; } + QString path() const {return mPath; } + }; + + class GateLibrarySelectionTable : public QAbstractTableModel + { + Q_OBJECT + QList mEntries; + bool mShowFullPath; + bool mWarnSubstitute; + public: + GateLibrarySelectionTable(bool addAutoDetect, QObject* parent = nullptr); + int columnCount(const QModelIndex& index = QModelIndex()) const override; + int rowCount(const QModelIndex& index = QModelIndex()) const override; + QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override; + void handleShowFullPath(bool checked); + int addGateLibrary(const QString& path); + QString gateLibraryPath(int inx) const; + int getIndexByPath(const QString& path); + bool isWarnSubstitute() const { return mWarnSubstitute; } + }; + + class GateLibrarySelection : public QFrame + { + Q_OBJECT + Q_PROPERTY(QString saveIconPath READ saveIconPath WRITE setSaveIconPath) + Q_PROPERTY(QString saveIconStyle READ saveIconStyle WRITE setSaveIconStyle) + QComboBox* mComboGatelib; + QPushButton* mInvokeFileDialog; + QLabel* mWarningMsg; + QCheckBox* mCheckFullPath; + QString mSaveIconPath; + QString mSaveIconStyle; + Q_SIGNALS: + void gatelibSelected(bool singleFile); + private Q_SLOTS: + void handleGatelibIndexChanged(int inx); + void handleInvokeFileDialog(); + void handleShowFullPath(bool checked); + public: + GateLibrarySelection(const QString& defaultGl, QWidget* parent = nullptr); + QString gateLibraryPath() const; + void setCurrent(const QString& glPath); + QString saveIconPath() const { return mSaveIconPath; } + QString saveIconStyle() const { return mSaveIconStyle; } + void setSaveIconPath(const QString& path) { mSaveIconPath = path; } + void setSaveIconStyle(const QString& sty) { mSaveIconStyle = sty; } + }; +} diff --git a/plugins/gui/include/gui/graph_widget/graph_context_manager.h b/plugins/gui/include/gui/graph_widget/graph_context_manager.h index 55634c4aedb..a0e84ba974a 100644 --- a/plugins/gui/include/gui/graph_widget/graph_context_manager.h +++ b/plugins/gui/include/gui/graph_widget/graph_context_manager.h @@ -115,6 +115,13 @@ namespace hal */ bool contextWithNameExists(const QString& name) const; + /** + * Generate next view with given prefix + * @param prefix + * @return the view name which does not exist so far + */ + QString nextViewName(const QString& prefix) const; + /** * Handler to be called after a module has been created. Used to apply the changes in the affected contexts.
* diff --git a/plugins/gui/include/gui/gui_api/gui_api.h b/plugins/gui/include/gui/gui_api/gui_api.h index 4b89f34c074..c01f4acdc7a 100644 --- a/plugins/gui/include/gui/gui_api/gui_api.h +++ b/plugins/gui/include/gui/gui_api/gui_api.h @@ -28,15 +28,45 @@ #include "hal_core/netlist/gate.h" #include "hal_core/netlist/net.h" #include "hal_core/netlist/module.h" +#include "gui/include/gui/gui_def.h" -#include -#include - #include +#include +#include +#include namespace hal { + namespace GuiApiClasses { + + class View{ + public: + static int isolateInNew(const std::vector, const std::vector); + static bool deleteView(int id); + static bool addTo(int id, const std::vector, const std::vector); + static bool removeFrom(int id, const std::vector, const std::vector); + static bool setName(int id, const std::string& name); + static int getId(const std::string& name); + static std::string getName(int id); + static std::vector getModules(int id); + static std::vector getGates(int id); + static std::vector getIds(const std::vector modules, const std::vector gates); + static bool foldModule(int view_id, Module* module); + static bool unfoldModule(int view_id, Module* module); + + struct ModuleGateIdPair { + QSet moduleIds; + QSet gateIds; + }; + + static ModuleGateIdPair getValidObjects(int viewId, const std::vector, const std::vector); + static GridPlacement* getGridPlacement(int viewId); + static bool setGridPlacement(int viewId, GridPlacement* gp); + }; + } + + /** * @ingroup gui * @brief Interface to interact with the gui itself. @@ -531,10 +561,12 @@ namespace hal */ void deselect(const std::vector& gates, const std::vector& nets, const std::vector& modules); + int isolateInNewView(const std::vector, const std::vector); + Q_SIGNALS: /** * Q_SIGNAL that is emitted whenever the view should be moved to a new selection. */ void navigationRequested(); - }; + }; } diff --git a/plugins/gui/include/gui/gui_def.h b/plugins/gui/include/gui/gui_def.h index 2ea85eab0c8..9c0825e2ce7 100644 --- a/plugins/gui/include/gui/gui_def.h +++ b/plugins/gui/include/gui/gui_def.h @@ -26,6 +26,8 @@ #pragma once #include "hal_core/defines.h" +#include "hal_core/utilities/log.h" +#include #include #include #include @@ -135,6 +137,53 @@ namespace hal uint qHash(const Node &n); + class GridPlacement : public QHash + { + public: + GridPlacement() {;} + void setGatePosition(u32 gateId, std::pairp, bool swap = false) { + QPoint pos = QPoint(gatePosition(gateId)->first, gatePosition(gateId)->second); //position of current gate to move + hal::Node nd = key(QPoint(p.first, p.second)); //find the node in the destination + + if(!nd.isNull() && !swap) //if the destination placement is not available + log_warning("gui", "Target position is already occupied"); + else if (!nd.isNull() && swap) + { + operator[](hal::Node(gateId,hal::Node::Gate)) = QPoint(p.first,p.second);//set the position of the first node to the destination + if(nd.isGate()) + operator[](hal::Node(nd.id(), hal::Node::Gate)) = pos;//set the position of the destination node to the position of the first node + else operator[](hal::Node(nd.id(), hal::Node::Module)) = pos; + } + else + operator[](hal::Node(gateId,hal::Node::Gate)) = QPoint(p.first,p.second); + } + void setModulePosition(u32 moduleId, std::pairp, bool swap = false){ + QPoint pos = QPoint(modulePosition(moduleId)->first, modulePosition(moduleId)->second); + hal::Node nd = key(QPoint(p.first, p.second)); + + if(!nd.isNull() && !swap) + log_warning("gui", "Target position is already occupied"); + else if (!nd.isNull() && swap) + { + operator[](hal::Node(moduleId,hal::Node::Module)) = QPoint(p.first,p.second); + if(nd.isGate()) + operator[](hal::Node(nd.id(), hal::Node::Gate)) = pos; + else operator[](hal::Node(nd.id(), hal::Node::Module)) = pos; + } + else + operator[](hal::Node(moduleId,hal::Node::Module)) = QPoint(p.first,p.second);}; + std::pair* gatePosition(u32 gateId) const + { + auto it = constFind(hal::Node(gateId,hal::Node::Gate)); + return (it == constEnd() ? nullptr : new std::pair(it->x(),it->y())); + } + std::pair* modulePosition(u32 moduleId) const + { + auto it = constFind(hal::Node(moduleId,hal::Node::Module)); + return (it == constEnd() ? nullptr : new std::pair(it->x(),it->y())); + } + }; + /** * @brief The PlacementHint class object provides hints for the layouter how new box objects * are placed on a view. In standard mode placement is done using the most compact squere-like @@ -151,13 +200,20 @@ namespace hal enum PlacementModeType {Standard = 0, PreferLeft = 1, PreferRight = 2, GridPosition = 3}; /** - * @brief PlacementHint constructor + * @brief PlacementHint standard constructor * @param mod placement mode must be either Standard or PreferLeft or PreferRight * @param orign node to start from when PreferLeft or PreferRight are set */ PlacementHint(PlacementModeType mod = Standard, const Node& orign=Node()) : mMode(mod), mPreferredOrigin(orign) {;} + /** + * @brief PlacementHint constructor for grid placement + * @param gridPlc Hash node to position + */ + PlacementHint(const GridPlacement& gridPlc) + : mMode(GridPosition), mPreferredOrigin(Node()), mGridPos(gridPlc) {;} + /** * @brief mode getter for placement mode type * @return either Standard or PreferLeft or PreferRight @@ -216,7 +272,7 @@ namespace hal private: PlacementModeType mMode; Node mPreferredOrigin; - QHash mGridPos; + GridPlacement mGridPos; }; /** diff --git a/plugins/gui/include/gui/main_window/main_window.h b/plugins/gui/include/gui/main_window/main_window.h index d2d28d7c3ba..667af66a91a 100644 --- a/plugins/gui/include/gui/main_window/main_window.h +++ b/plugins/gui/include/gui/main_window/main_window.h @@ -25,13 +25,12 @@ #pragma once -#include "hal_core/utilities/program_options.h" - -#include "gui/content_layout_area/content_layout_area.h" -#include "hal_core/utilities/program_options.h" #include "gui/action/action.h" +#include "gui/content_layout_area/content_layout_area.h" #include "gui/settings/main_settings_widget.h" #include "gui/splitter/splitter.h" +#include "hal_core/netlist/module.h" +#include "hal_core/utilities/program_options.h" #include #include @@ -614,7 +613,7 @@ namespace hal Action* mActionImportNetlist; Action* mActionSave; Action* mActionSaveAs; - Action* mActionGateLibraryManager; +// Action* mActionGateLibraryManager; Action* mActionAbout; Action* mActionStartRecording; Action* mActionStopRecording; diff --git a/plugins/gui/include/gui/main_window/plugin_parameter_dialog.h b/plugins/gui/include/gui/main_window/plugin_parameter_dialog.h index 110335790b1..d4374690646 100644 --- a/plugins/gui/include/gui/main_window/plugin_parameter_dialog.h +++ b/plugins/gui/include/gui/main_window/plugin_parameter_dialog.h @@ -30,11 +30,14 @@ #include "hal_core/plugin_system/gui_extension_interface.h" #include #include +#include class QFormLayout; class QDialogButtonBox; class QPushButton; class QLineEdit; +class QSpinBox; +class QLabel; namespace hal { @@ -55,6 +58,27 @@ namespace hal { QString getFilename() const; }; + class PluginParameterNodeDialog : public QWidget + { + Q_OBJECT + + PluginParameter mParameter; + QPushButton* mButton; + QSpinBox* mNodeId; + QLabel* mNodeName; + std::set mValidIds; + private Q_SLOTS: + void handleActivateModuleDialog(); + void handleActivateGateDialog(); + private: + void setModule(int id); + void setGate(int id); + bool isValidId(int id) const; + public: + PluginParameterNodeDialog(const PluginParameter& par, QWidget* parent = nullptr); + int getNodeId() const; + }; + class PluginParameterDialog : public QDialog { Q_OBJECT diff --git a/plugins/gui/include/gui/user_action/action_move_node.h b/plugins/gui/include/gui/user_action/action_move_node.h index ecb91776b77..a7b8c291259 100644 --- a/plugins/gui/include/gui/user_action/action_move_node.h +++ b/plugins/gui/include/gui/user_action/action_move_node.h @@ -41,34 +41,52 @@ namespace hal */ class ActionMoveNode : public UserAction { - QPoint mFrom, mTo; + u32 mContextId; + QPoint mTo; + bool mSwap; - int mContextId; + GridPlacement mGridPlacement; static QPoint parseFromString(const QString& s); + + bool checkContextId(); public: + /** + * empty constructor + * @param ctxId + */ + + /*ActionMoveNode(u32 ctxId) + :mContextId(ctxId) {;}*/ /** * Action constructor. * - * The action object is the graphics context - * + * The action object will be set by 'from' node + * @param ctxID - context * @param from - The initial position of the node to move * @param to - The destination of the node */ - ActionMoveNode(const QPoint& from = QPoint(), const QPoint& to = QPoint()) - : mFrom(from), mTo(to), mContextId(-1) {;} + ActionMoveNode(u32 ctxID, const QPoint& from, const QPoint& to, bool swap = false); /** * Action constructor. * - * Will move the node object (gate or module) in view identified by context - * A call to setObject is required to identify the node object - * - * @param - The initial position of the node to move + * The action object is the node to be moved + * @param ctxID - context * @param to - The destination of the node */ - ActionMoveNode(u32 ctxId, const QPoint& to) - : mTo(to), mContextId(ctxId) {;} + ActionMoveNode(u32 ctxID, const QPoint& to); + + /** + * Action constructor. + * + * Will move the nodes according to GridPlacement position info, + * no action object should be given + * + * @param ctxID - context + * @param gridPlc - The grid placement instance to copy (if any) + */ + ActionMoveNode(u32 ctxId=0, const GridPlacement* gridPlc=nullptr); bool exec() override; QString tagname() const override; diff --git a/plugins/gui/include/gui/user_action/user_action_manager.h b/plugins/gui/include/gui/user_action/user_action_manager.h index b7da620f918..7812eb019c7 100644 --- a/plugins/gui/include/gui/user_action/user_action_manager.h +++ b/plugins/gui/include/gui/user_action/user_action_manager.h @@ -32,6 +32,7 @@ #include #include #include +#include #include "hal_core/defines.h" namespace hal @@ -138,6 +139,8 @@ namespace hal */ void crashDump(int sig); + void executeActionBlockThread(UserAction* act); + private: UserActionManager(QObject *parent = nullptr); void testUndo(); @@ -151,6 +154,8 @@ namespace hal static UserActionManager* inst; QPlainTextEdit* mDumpAction; SettingsItemCheckbox* mSettingDumpAction; + UserAction* mThreadedAction; + QMutex mMutex; public Q_SLOTS: /** @@ -160,6 +165,9 @@ namespace hal */ void handleSettingDumpActionChanged(bool wantDump); + private Q_SLOTS: + void handleTriggerExecute(); + Q_SIGNALS: /** * Q_SIGNAL that is emitted when undoLastAction() is called. The parameter is set to true @@ -168,5 +176,7 @@ namespace hal * @param yesWeCan - True if last action could be undone. */ void canUndoLastAction(bool yesWeCan); + + void triggerExecute(); }; } diff --git a/plugins/gui/resources/stylesheet/dark.qss b/plugins/gui/resources/stylesheet/dark.qss index a6e56ff8697..4cbe8ff2d47 100755 --- a/plugins/gui/resources/stylesheet/dark.qss +++ b/plugins/gui/resources/stylesheet/dark.qss @@ -884,6 +884,22 @@ hal--OpenFileWidget #icon-label margin-top : 220px; } +hal--GateLibrarySelection +{ + qproperty-saveIconStyle: "all->#3192C5"; + qproperty-saveIconPath: ":/icons/folder-down"; +} + +hal--GateLibrarySelection QLabel +{ + background: #171e22; +} + +hal--GateLibrarySelection QLabel#warningMsg +{ + color: yellow; +} + hal--ImportNetlistDialog { qproperty-saveIconStyle: "all->#3192C5"; diff --git a/plugins/gui/resources/stylesheet/light.qss b/plugins/gui/resources/stylesheet/light.qss index 1114a058322..ce3511af89b 100755 --- a/plugins/gui/resources/stylesheet/light.qss +++ b/plugins/gui/resources/stylesheet/light.qss @@ -911,6 +911,22 @@ hal--OpenFileWidget #icon-label margin-top : 220px; } +hal--GateLibrarySelection +{ + qproperty-saveIconStyle: "all->#3192C5"; + qproperty-saveIconPath: ":/icons/folder-down"; +} + +hal--GateLibrarySelection QLabel +{ + background: white; +} + +hal--GateLibrarySelection QLabel#warningMsg +{ + color: darkred; +} + hal--ImportNetlistDialog { qproperty-saveIconStyle: "all->#3192C5"; diff --git a/plugins/gui/src/expanding_list/expanding_list_widget.cpp b/plugins/gui/src/expanding_list/expanding_list_widget.cpp index 16af6b1a444..6b84695835f 100644 --- a/plugins/gui/src/expanding_list/expanding_list_widget.cpp +++ b/plugins/gui/src/expanding_list/expanding_list_widget.cpp @@ -45,6 +45,7 @@ namespace hal void ExpandingListWidget::clearSelected() { + if (!mSelectedButton) return; mSelectedButton->setSelected(false); mSelectedButton = nullptr; } diff --git a/plugins/gui/src/export/import_project_dialog.cpp b/plugins/gui/src/export/import_project_dialog.cpp index 2c9566cd085..e9b1ace4cb9 100644 --- a/plugins/gui/src/export/import_project_dialog.cpp +++ b/plugins/gui/src/export/import_project_dialog.cpp @@ -2,10 +2,13 @@ #include "gui/file_manager/file_manager.h" #include "gui/gui_utils/graphics.h" #include "hal_core/utilities/log.h" +#include #include #include #include #include +#include +#include #include namespace hal { @@ -68,6 +71,12 @@ namespace hal { layout->addStretch(); + layout->addWidget(new QLabel("Decompressed HAL project name:")); + mExtractProjectEdit = new QLineEdit(this); + layout->addWidget(mExtractProjectEdit); + + layout->addStretch(); + mButtonBox = new QDialogButtonBox(QDialogButtonBox::Ok|QDialogButtonBox::Cancel,this); connect(mButtonBox, &QDialogButtonBox::accepted, this, &QDialog::accept); connect(mButtonBox, &QDialogButtonBox::rejected, this, &QDialog::reject); @@ -77,7 +86,26 @@ namespace hal { void ImportProjectDialog::handleSelectionStatusChanged() { - mButtonBox->button(QDialogButtonBox::Ok)->setEnabled(mZippedFile->valid() && mTargetDirectory->valid()); + bool valid = mTargetDirectory->valid() && mZippedFile->valid(); + if (valid) + { + QRegularExpression reProj("(.*)/\\.project.json$"); + QStringList zipFlist = JlCompress::getFileList(mZippedFile->selection()); + int inx = zipFlist.indexOf(reProj); + if (inx < 0) + { + QMessageBox::warning(this,"Invalid ZIP-file", "The zipped file " + mZippedFile->selection() + " does not seem to be a hal project.\n"); + valid = false; + } + else + { + QRegularExpressionMatch match = reProj.match(zipFlist.at(inx)); + mTargetProjectName = match.captured(1); + QString suggestedDir = suggestedProjectDir(QDir(mTargetDirectory->selection()).absoluteFilePath(mTargetProjectName)); + mExtractProjectEdit->setText(QFileInfo(suggestedDir).fileName()); + } + } + mButtonBox->button(QDialogButtonBox::Ok)->setEnabled(valid); } bool ImportProjectDialog::importProject() @@ -85,15 +113,32 @@ namespace hal { mStatus = NoFileSelected; if (mZippedFile->selection().isEmpty()) return false; + mExtractedProjectAbsolutePath = QDir(mTargetDirectory->selection()).absoluteFilePath(mExtractProjectEdit->text()); + if (QFileInfo(mExtractedProjectAbsolutePath).exists()) + { + QMessageBox::warning(this, "Cannot Create Project", "Direcotry " + mExtractedProjectAbsolutePath + " already exists"); + return false; + } + QStringList extracted; + QString tempdir; + if (mExtractProjectEdit->text() != mTargetProjectName) + { + tempdir = QDir(mTargetDirectory->selection()).absoluteFilePath("hal_temp_" + QUuid::createUuid().toString(QUuid::WithoutBraces)); + QDir().mkpath(tempdir); + extracted = JlCompress::extractDir(mZippedFile->selection(),tempdir); + } + else + { + extracted = JlCompress::extractDir(mZippedFile->selection(),mTargetDirectory->selection()); + } mStatus = ErrorDecompress; - QStringList extracted = JlCompress::extractDir(mZippedFile->selection(),mTargetDirectory->selection()); if (extracted.isEmpty()) return false; - mExtractedProjectDir = extracted.at(0); - while (mExtractedProjectDir.back().unicode() == '/' || mExtractedProjectDir.back().unicode() == '\\') - mExtractedProjectDir.chop(1); + QString extractedDir = extracted.at(0); + while (extractedDir.back().unicode() == '/' || extractedDir.back().unicode() == '\\') + extractedDir.chop(1); - switch (FileManager::directoryStatus(mExtractedProjectDir)) + switch (FileManager::directoryStatus(extractedDir)) { case FileManager::ProjectDirectory: mStatus = Ok; @@ -127,38 +172,87 @@ namespace hal { mStatus = NotAHalProject; break; case FileManager::UnknownDirectoryEntry: - log_warning("gui", "Failed to decompress archiv '{}', result is neither a file nor a directory.", mZippedFile->selection().toStdString()); + log_warning("gui", "Failed to decompress archive '{}', result is neither a file nor a directory.", mZippedFile->selection().toStdString()); mStatus = NotAHalProject; break; } - if (mStatus != Ok) + if (!tempdir.isEmpty()) { - // Not a hal project, delete extracted - auto it = extracted.begin(); - while (it != extracted.end()) - if (QFileInfo(*it).isDir()) - ++it; - else - { - QFile::remove(*it); - it = extracted.erase(it); - } - - // Delete directories in revers order - if (!extracted.isEmpty()) + if (!QFile::rename(extractedDir,mExtractedProjectAbsolutePath)) { - for (;;) - { - --it; - QFile::remove(*it); - if (it == extracted.begin()) - break; - } + log_warning("gui", "Failed to move decompressed archive to new location '{}'.", mExtractedProjectAbsolutePath.toStdString()); + mStatus = ErrorDecompress; } + else + { + // delete everything left in tempdir after moving project to project dir + deleteFilesRecursion(tempdir); + } + } + + if (mStatus != Ok) + { + // Not a hal project, delete all extracted + deleteFilesList(extracted); return false; } return true; } + + void ImportProjectDialog::deleteFilesRecursion(QString dir) + { + for (QFileInfo finfo : QDir(dir).entryInfoList(QDir::AllEntries | QDir::Hidden | QDir::NoDotAndDotDot)) + { + if (finfo.isDir()) + deleteFilesRecursion(finfo.absoluteFilePath()); + else + QFile::remove(finfo.absoluteFilePath()); + } + QDir().rmdir(dir); + } + + void ImportProjectDialog::deleteFilesList(QStringList files) + { + // Delete files, keep directories (might be not empty) + auto it = files.begin(); + while (it != files.end()) + if (QFileInfo(*it).isDir()) + ++it; + else + { + QFile::remove(*it); + it = files.erase(it); + } + + // Delete directories in revers order + if (!files.isEmpty()) + { + for (;;) + { + --it; + QFile::remove(*it); + if (it == files.begin()) + break; + } + } + } + + QString ImportProjectDialog::suggestedProjectDir(const QString& filename) + { + QString retval = filename; + retval.remove(QRegularExpression("\\.\\w*$")); + + QString basedir = retval; + int count = 2; + + for (;;) // loop until non existing directory found + { + if (!QFileInfo(retval).exists()) + return retval; + retval = QString("%1_%2").arg(basedir).arg(count++); + } + return retval; + } } diff --git a/plugins/gui/src/file_manager/file_manager.cpp b/plugins/gui/src/file_manager/file_manager.cpp index bcc3ab9774e..ec3ccffee68 100644 --- a/plugins/gui/src/file_manager/file_manager.cpp +++ b/plugins/gui/src/file_manager/file_manager.cpp @@ -494,6 +494,9 @@ namespace hal { QString logical_file_name = filename; + QString glSuffix = QFileInfo(gatelibraryPath).suffix(); + gPluginRelay->mGuiPluginTable->loadFeature(FacExtensionInterface::FacGatelibParser,"."+glSuffix); + if (gNetlist) { // ADD ERROR MESSAGE @@ -521,23 +524,27 @@ namespace hal QFileInfo nlInfo(filename); if (nlInfo.suffix()=="hal") { - // event_controls::enable_all(false); won't get events until callbacks are registered - auto netlist = netlist_factory::load_netlist(filename.toStdString()); - // event_controls::enable_all(true); - if (netlist) + QHash errorCount; + for (;;) // try loading hal file until exit by return { - gNetlistOwner = std::move(netlist); - gNetlist = gNetlistOwner.get(); - gNetlistRelay->registerNetlistCallbacks(); - fileSuccessfullyLoaded(logical_file_name); - return true; - } - else - { - std::string error_message("Failed to create netlist from .hal file"); - log_error("gui", "{}", error_message); - displayErrorMessage(QString::fromStdString(error_message)); - return false; + // event_controls::enable_all(false); won't get events until callbacks are registered + auto netlist = netlist_factory::load_netlist(filename.toStdString(),gatelibraryPath.toStdString()); + // event_controls::enable_all(true); + if (netlist) + { + gNetlistOwner = std::move(netlist); + gNetlist = gNetlistOwner.get(); + gNetlistRelay->registerNetlistCallbacks(); + fileSuccessfullyLoaded(logical_file_name); + return true; + } + else + { + std::string error_message("Failed to create netlist from .hal file"); + log_error("gui", "{}", error_message); + displayErrorMessage(QString::fromStdString(error_message)); + return false; + } } } else diff --git a/plugins/gui/src/file_manager/import_netlist_dialog.cpp b/plugins/gui/src/file_manager/import_netlist_dialog.cpp index b0c788ed1ca..921d02cdd50 100644 --- a/plugins/gui/src/file_manager/import_netlist_dialog.cpp +++ b/plugins/gui/src/file_manager/import_netlist_dialog.cpp @@ -1,24 +1,22 @@ #include "gui/file_manager/import_netlist_dialog.h" #include "gui/gui_utils/graphics.h" -#include "hal_core/netlist/gate_library/gate_library.h" -#include "hal_core/netlist/gate_library/gate_library_manager.h" +#include "gui/export/import_project_dialog.h" +#include "gui/gatelibrary_management/gatelibrary_selection.h" #include -#include #include #include #include #include -#include #include #include #include #include #include #include -#include #include +#include namespace hal { @@ -27,8 +25,7 @@ namespace hal QStyle* s = style(); s->unpolish(this); s->polish(this); - QString suggestedGateLibraryPath; - QString suggestedGateLibraryName; + QString gatelibFromHal; if (filename.endsWith(".hal")) { QFile halFile(filename); @@ -40,7 +37,7 @@ namespace hal { const QJsonObject& nlObj = halObj["netlist"].toObject(); if (nlObj.contains("gate_library") && nlObj["gate_library"].isString()) - suggestedGateLibraryPath = nlObj["gate_library"].toString(); + gatelibFromHal = nlObj["gate_library"].toString(); } } } @@ -53,7 +50,7 @@ namespace hal layout->setRowStretch(irow - 1, 20); layout->addWidget(new QLabel("Location of project directory:", this), irow++, 0, Qt::AlignLeft); - setSuggestedProjectDir(filename); + mProjectdir = ImportProjectDialog::suggestedProjectDir(filename); QFrame* frameProjectdir = new QFrame(this); QHBoxLayout* layProjectdir = new QHBoxLayout(frameProjectdir); @@ -66,42 +63,9 @@ namespace hal layout->addWidget(frameProjectdir, irow++, 0); layout->addItem(new QSpacerItem(30, 30), irow++, 0); - QLabel* labGatelib = new QLabel("Gate library:", this); - layout->addWidget(labGatelib, irow++, 0, Qt::AlignLeft); - mComboGatelib = new QComboBox(this); - if (filename.endsWith(".hal")) - { - mComboGatelib->setDisabled(true); - labGatelib->setDisabled(true); - } - else - { - mComboGatelib->addItem("(Auto detect)"); - for (const std::filesystem::path& path : gate_library_manager::get_all_path()) - { - int n = mGateLibraryPath.size(); - QString qName = QString::fromStdString(path.filename()); - mGateLibraryMap.insert(qName, n); - QString qPath = QString::fromStdString(path.string()); - mGateLibraryPath.append(qPath); - if (qPath == suggestedGateLibraryPath) - suggestedGateLibraryName = qName; - } - if (suggestedGateLibraryName.isEmpty() && !suggestedGateLibraryPath.isEmpty()) - { - // suggested gate library not found in default path - QFileInfo info(suggestedGateLibraryPath); - suggestedGateLibraryName = info.fileName(); - int n = mGateLibraryPath.size(); - mGateLibraryMap.insert(suggestedGateLibraryName, n); - mGateLibraryPath.append(suggestedGateLibraryPath); - } - mComboGatelib->addItems(mGateLibraryMap.keys()); - if (!suggestedGateLibraryName.isEmpty()) - mComboGatelib->setCurrentText(suggestedGateLibraryName); - } - mComboGatelib->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); - layout->addWidget(mComboGatelib, irow++, 0); + mGatelibSelection = new GateLibrarySelection(gatelibFromHal, this); + layout->addWidget(mGatelibSelection, irow++, 0); + //mGatelibSelection->setDisabled(filename.endsWith(".hal")); layout->addItem(new QSpacerItem(30, 30), irow++, 0); layout->setRowStretch(irow - 1, 20); @@ -115,24 +79,6 @@ namespace hal connect(dbb, &QDialogButtonBox::accepted, this, &QDialog::accept); connect(dbb, &QDialogButtonBox::rejected, this, &QDialog::reject); layout->addWidget(dbb, irow++, 0, Qt::AlignRight); - connect(mComboGatelib, &QComboBox::currentTextChanged, this, &ImportNetlistDialog::handleGateLibraryPathChanged); - handleGateLibraryPathChanged(mComboGatelib->currentText()); - } - - void ImportNetlistDialog::setSuggestedProjectDir(const QString& filename) - { - mProjectdir = filename; - mProjectdir.remove(QRegularExpression("\\.\\w*$")); - - QString basedir = mProjectdir; - int count = 2; - - for (;;) // loop until non existing directory found - { - if (!QFileInfo(mProjectdir).exists()) - return; - mProjectdir = QString("%1_%2").arg(basedir).arg(count++); - } } QString ImportNetlistDialog::projectDirectory() const @@ -148,24 +94,22 @@ namespace hal mEditProjectdir->setText(dir); } - void ImportNetlistDialog::handleGateLibraryPathChanged(const QString& txt) + void ImportNetlistDialog::handleGatelibSelected(bool singleFile) { - if (mGateLibraryMap.value(txt, -1) < 0) + if (singleFile) + { + mCheckCopyGatelib->setEnabled(true); + } + else { mCheckCopyGatelib->setCheckState(Qt::Unchecked); mCheckCopyGatelib->setDisabled(true); } - else - mCheckCopyGatelib->setEnabled(true); } QString ImportNetlistDialog::gateLibraryPath() const { - QString seltxt = mComboGatelib->currentText(); - int inx = mGateLibraryMap.value(seltxt, -1); - if (inx < 0) - return QString(); - return mGateLibraryPath.at(inx); + return mGatelibSelection->gateLibraryPath(); } bool ImportNetlistDialog::isMoveNetlistChecked() const @@ -177,4 +121,5 @@ namespace hal { return mCheckCopyGatelib->isChecked(); } + } // namespace hal diff --git a/plugins/gui/src/file_manager/new_project_dialog.cpp b/plugins/gui/src/file_manager/new_project_dialog.cpp index 3a375928d68..4ea28bc4f81 100644 --- a/plugins/gui/src/file_manager/new_project_dialog.cpp +++ b/plugins/gui/src/file_manager/new_project_dialog.cpp @@ -13,8 +13,6 @@ #include #include #include -#include -#include #include #include #include diff --git a/plugins/gui/src/gatelibrary_management/gatelibrary_selection.cpp b/plugins/gui/src/gatelibrary_management/gatelibrary_selection.cpp new file mode 100644 index 00000000000..bb2d3f20a9f --- /dev/null +++ b/plugins/gui/src/gatelibrary_management/gatelibrary_selection.cpp @@ -0,0 +1,221 @@ +#include "gui/gatelibrary_management/gatelibrary_selection.h" +#include "gui/gui_utils/graphics.h" +#include "hal_core/netlist/gate_library/gate_library.h" +#include "hal_core/netlist/gate_library/gate_library_manager.h" +#include "hal_core/utilities/log.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace hal { + GateLibrarySelection::GateLibrarySelection(const QString &defaultGl, QWidget* parent) + : QFrame(parent) + { + QStyle* s = style(); + s->unpolish(this); + s->polish(this); + + QVBoxLayout* vlayout = new QVBoxLayout(this); + QLabel* labGatelib = new QLabel("Gate library:", this); + vlayout->addWidget(labGatelib); + + + QHBoxLayout* hlayout = new QHBoxLayout; + setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); + setFrameStyle(QFrame::Sunken | QFrame::Panel); + + mComboGatelib = new QComboBox(this); + GateLibrarySelectionTable* glTable = new GateLibrarySelectionTable(defaultGl.isEmpty(),this); + mComboGatelib->setModel(glTable); + + mComboGatelib->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); + + hlayout->addWidget(mComboGatelib); + + mWarningMsg = new QLabel(this); + mWarningMsg->setObjectName("warningMsg"); + if (!defaultGl.isEmpty()) + { + int inx = glTable->getIndexByPath(defaultGl); + if (inx < 0) + { + mWarningMsg->setText("Gate library '" + defaultGl + "' not found,\nplease select gate library from list."); + } + else + { + mComboGatelib->setCurrentIndex(inx); + if (glTable->isWarnSubstitute()) + mWarningMsg->setText("Gate library '" + defaultGl + "' not found,\na substitute has been suggested."); + } + } + + + mInvokeFileDialog = new QPushButton(gui_utility::getStyledSvgIcon(mSaveIconStyle, mSaveIconPath),"",this); + connect(mInvokeFileDialog, &QPushButton::clicked, this, &GateLibrarySelection::handleInvokeFileDialog); + hlayout->addWidget(mInvokeFileDialog); + + vlayout->addLayout(hlayout); + vlayout->addWidget(mWarningMsg); + if (mWarningMsg->text().isEmpty()) + mWarningMsg->hide(); + + mCheckFullPath = new QCheckBox("Show full path"); + mCheckFullPath->setChecked(false); + connect(mCheckFullPath,&QCheckBox::toggled,this,&GateLibrarySelection::handleShowFullPath); + vlayout->addWidget(mCheckFullPath); + connect(mComboGatelib,QOverload::of(&QComboBox::currentIndexChanged),this,&GateLibrarySelection::handleGatelibIndexChanged); + } + + void GateLibrarySelection::handleGatelibIndexChanged(int inx) + { + Q_UNUSED(inx); + mWarningMsg->clear(); + mWarningMsg->hide(); + } + + void GateLibrarySelection::handleInvokeFileDialog() + { + QString glFilename = QFileDialog::getOpenFileName(this, "Select Gate Library", QDir::currentPath(), "HGL Files (*.hgl);;Lib Files (*.lib)"); + if (glFilename.isEmpty()) return; + int inx = static_cast(mComboGatelib->model())->addGateLibrary(glFilename); + mComboGatelib->setCurrentIndex(inx); + } + + QString GateLibrarySelection::gateLibraryPath() const + { + const GateLibrarySelectionTable* glst = static_cast(mComboGatelib->model()); + return glst->gateLibraryPath(mComboGatelib->currentIndex()); + } + + void GateLibrarySelection::handleShowFullPath(bool checked) + { + int inx = mComboGatelib->currentIndex(); + static_cast(mComboGatelib->model())->handleShowFullPath(checked); + mComboGatelib->setCurrentIndex(inx); + } + + //----------------------------------------------- + + QVariant GateLibrarySelectionEntry::data(int column, bool fullPath) const + { + if (fullPath) + { + if (mCount<0) + return mName; + return mPath; + } + + switch(column) + { + case 0: + if (mCount <= 0) return mName; + return QString("%1 (%2)").arg(mName).arg(mCount+1); + case 1: return mPath; + } + return QVariant(); + } + + GateLibrarySelectionTable::GateLibrarySelectionTable(bool addAutoDetect, QObject *parent) + : QAbstractTableModel(parent), mShowFullPath(false), mWarnSubstitute(false) + { + if (addAutoDetect) + mEntries.append(GateLibrarySelectionEntry("(Auto detect)", "", -1)); + else + mEntries.append(GateLibrarySelectionEntry("", "", -1)); + QMap nameMap; + for (const std::filesystem::path& path : gate_library_manager::get_all_path()) + { + QString name = QString::fromStdString(path.filename()); + mEntries.append(GateLibrarySelectionEntry(name, QString::fromStdString(path.string()), nameMap[name]++)); + } + } + + int GateLibrarySelectionTable::columnCount(const QModelIndex& index) const + { + Q_UNUSED(index); + return 2; + } + + int GateLibrarySelectionTable::rowCount(const QModelIndex& index) const + { + Q_UNUSED(index); + return mEntries.size(); + } + + QVariant GateLibrarySelectionTable::data(const QModelIndex& index, int role) const + { + if (role != Qt::DisplayRole) return QVariant(); + if (index.row() >= mEntries.size()) return QVariant(); + return (mEntries.at(index.row()).data(index.column(),mShowFullPath)); + } + + int GateLibrarySelectionTable::addGateLibrary(const QString &path) + { + int inx = 0; + QMap nameMap; + + for (const GateLibrarySelectionEntry& glse : mEntries) + { + if (glse.path() == path) return inx; // path already in table + nameMap[glse.name()]++; + inx++; + } + + QString name = QFileInfo(path).fileName(); + beginResetModel(); + mEntries.append(GateLibrarySelectionEntry(name,path,nameMap[name]++)); + return inx; + } + + int GateLibrarySelectionTable::getIndexByPath(const QString& path) + { + // try exact path + int inx = 0; + for (const GateLibrarySelectionEntry& glse : mEntries) + { + if (glse.path() == path) return inx; + ++inx; + } + + // existing gatelib path not in list : add it + if (QFileInfo(path).exists()) + return addGateLibrary(path); + + // try name + QString name = QFileInfo(path).fileName(); + inx = 0; + for (const GateLibrarySelectionEntry& glse : mEntries) + { + if (glse.name() == name) + { + log_info("gui", "Requested gate library '{}' not found, suggest '{}' instead.", path.toStdString(), glse.path().toStdString()); + mWarnSubstitute = true; + return inx; + } + ++inx; + } + + log_info("gui", "Requested gate library '{}' not found.", path.toStdString()); + return -1; + } + + + QString GateLibrarySelectionTable::gateLibraryPath(int inx) const + { + if (inx < 0 || inx >= mEntries.size()) return QString(); + return mEntries.at(inx).path(); + } + + void GateLibrarySelectionTable::handleShowFullPath(bool checked) + { + beginResetModel(); + mShowFullPath = checked; + endResetModel(); + } +} diff --git a/plugins/gui/src/graph_widget/graph_context_manager.cpp b/plugins/gui/src/graph_widget/graph_context_manager.cpp index e050e5b9452..399759a76ce 100644 --- a/plugins/gui/src/graph_widget/graph_context_manager.cpp +++ b/plugins/gui/src/graph_widget/graph_context_manager.cpp @@ -127,6 +127,17 @@ namespace hal return nullptr; } + QString GraphContextManager::nextViewName(const QString& prefix) const + { + int cnt = 0; + + for (;;) + { + QString name = QString("%1 %2").arg(prefix).arg(++cnt); + if (!contextWithNameExists(name)) return name; + } + } + bool GraphContextManager::contextWithNameExists(const QString& name) const { for (GraphContext* ctx : mContextTableModel->list()) diff --git a/plugins/gui/src/graph_widget/graph_graphics_view.cpp b/plugins/gui/src/graph_widget/graph_graphics_view.cpp index 198ba9de57f..0da4263517a 100644 --- a/plugins/gui/src/graph_widget/graph_graphics_view.cpp +++ b/plugins/gui/src/graph_widget/graph_graphics_view.cpp @@ -154,25 +154,14 @@ namespace hal return; } - u32 cnt = 0; - while (true) - { - ++cnt; - QString name = "Isolated View " + QString::number(cnt); - - if (!gGraphContextManager->contextWithNameExists(name)) - { - UserActionCompound* act = new UserActionCompound; - act->setUseCreatedObject(); - act->addAction(new ActionCreateObject(UserActionObjectType::Context, name)); - act->addAction(new ActionAddItemsToObject(selected_modules, selected_gates)); - act->exec(); - GraphContext* context = gGraphContextManager->getContextById(act->object().id()); - context->setDirty(false); - - return; - } - } + QString name = gGraphContextManager->nextViewName("Isolated View"); + UserActionCompound* act = new UserActionCompound; + act->setUseCreatedObject(); + act->addAction(new ActionCreateObject(UserActionObjectType::Context, name)); + act->addAction(new ActionAddItemsToObject(selected_modules, selected_gates)); + act->exec(); + GraphContext* context = gGraphContextManager->getContextById(act->object().id()); + context->setDirty(false); } void GraphGraphicsView::handleMoveAction(u32 moduleId) @@ -600,8 +589,7 @@ namespace hal } else { - ActionMoveNode* act = new ActionMoveNode(sourceLayouterPos,targetLayouterPos); - act->setObject(UserActionObject(context->id(),UserActionObjectType::Context)); + ActionMoveNode* act = new ActionMoveNode(context->id(),sourceLayouterPos,targetLayouterPos); act->exec(); } context->setDirty(true); diff --git a/plugins/gui/src/graph_widget/graphics_scene.cpp b/plugins/gui/src/graph_widget/graphics_scene.cpp index 0303ae441a2..78d40b965d3 100644 --- a/plugins/gui/src/graph_widget/graphics_scene.cpp +++ b/plugins/gui/src/graph_widget/graphics_scene.cpp @@ -708,6 +708,7 @@ namespace hal void GraphicsScene::debugDrawLayouterGrid(QPainter* painter, const int x_from, const int x_to, const int y_from, const int y_to) { + if (mDebugXLines.isEmpty() || mDebugYLines.isEmpty()) return; painter->setPen(QPen(Qt::magenta)); for (qreal x : mDebugXLines) diff --git a/plugins/gui/src/gui_api/gui_api.cpp b/plugins/gui/src/gui_api/gui_api.cpp index ef6528e7dcb..07734133b79 100644 --- a/plugins/gui/src/gui_api/gui_api.cpp +++ b/plugins/gui/src/gui_api/gui_api.cpp @@ -1,6 +1,21 @@ #include "gui/gui_api/gui_api.h" + #include "gui/gui_globals.h" +#include "gui/user_action/user_action_compound.h" +#include "gui/user_action/user_action_object.h" +#include "gui/user_action/user_action_manager.h" +#include "gui/user_action/action_create_object.h" +#include "gui/user_action/action_delete_object.h" +#include "gui/user_action/action_add_items_to_object.h" +#include "gui/user_action/action_remove_items_from_object.h" +#include "gui/user_action/action_rename_object.h" +#include "gui/user_action/action_fold_module.h" +#include "gui/user_action/action_unfold_module.h" +#include "gui/user_action/action_move_node.h" +#include "gui/graph_widget/graph_context_manager.h" +#include "gui/context_manager_widget/models/context_table_model.h" +#include "hal_core/utilities/log.h" #include @@ -428,4 +443,496 @@ namespace hal { gSelectionRelay->clearAndUpdate(); } + + + int GuiApiClasses::View::isolateInNew(std::vector modules, std::vector gates) + { + //check if the inputs are valid + for(Module* mod : modules) + { + if (mod == nullptr) + { + log_warning("gui","Null values not allowed in module argument"); + return 0; + } + } + + for(Gate* gate : gates) + { + if (gate == nullptr) + { + log_warning("gui","Null values not allowed in gate argument"); + return 0; + } + } + QString name; + + bool isModuleExclusive = false; + + //make sure that modules and gates are not empty + if(modules.empty() && gates.empty()) + return 0; + + //Check if view should be bound exclusively to module + if(modules.size() == 1 && gates.empty() && modules[0]){ + name = QString::fromStdString(modules[0]->get_name()) + QString(" (ID: %1)").arg(modules[0]->get_id()); + isModuleExclusive = true; + //If the view already exists then return existing id + if(gGraphContextManager->contextWithNameExists(name)){ + for(GraphContext* ctx : gGraphContextManager->getContexts()){ + if(ctx->name() == name){ + return ctx->id(); + } + } + return 0; + } + } + else + { + //Get the number which has to be appended to name + name = gGraphContextManager->nextViewName("Isolated View"); + } + GuiApiClasses::View::ModuleGateIdPair pair = GuiApiClasses::View::getValidObjects(0, modules, gates); + + // Unpack return values and check if not empty + QSet moduleIds = pair.moduleIds; + QSet gateIds = pair.gateIds; + + if(moduleIds.isEmpty() && gateIds.isEmpty()) + return 0; + + UserActionCompound* act = new UserActionCompound; + act->setUseCreatedObject(); + act->addAction(new ActionCreateObject(UserActionObjectType::Context, name)); + act->addAction(new ActionAddItemsToObject(moduleIds, gateIds)); + UserActionManager::instance()->executeActionBlockThread(act); + + if (isModuleExclusive){ + GraphContext* context = gGraphContextManager->getContextById(act->object().id()); + context->setDirty(false); + context->setExclusiveModuleId(modules[0]->get_id()); + } + return act->object().id(); + } + + bool GuiApiClasses::View::deleteView(int id) + { + // check if id is valid + if(gGraphContextManager->getContextById(id) == nullptr) + { + return false; + } + + ActionDeleteObject* act = new ActionDeleteObject(); + act->setObject(UserActionObject(id, UserActionObjectType::Context)); + UserActionManager::instance()->executeActionBlockThread(act); + return true; + } + + + bool GuiApiClasses::View::addTo(int id, const std::vector modules, const std::vector gates) + { + //check if the inputs are valid + for(Module* mod : modules) + { + if (mod == nullptr) + { + log_warning("gui","Null values not allowed in module argument"); + return 0; + } + } + + for(Gate* gate : gates) + { + if (gate == nullptr) + { + log_warning("gui","Null values not allowed in gate argument"); + return 0; + } + } + + if (!gGraphContextManager->getContextById(id)) return false; // context does not exist + + + if (gGraphContextManager->getContextById(id)->isShowingModuleExclusively() && gGraphContextManager->getContextById(id)->getExclusiveModuleId() == gNetlist->get_top_module()->get_id()) + return false; //context shows topmodule so we can not add anything to it + + GuiApiClasses::View::ModuleGateIdPair pair = GuiApiClasses::View::getValidObjects(id, modules, gates); + + // Get ids from modules and gates + QSet moduleIds = pair.moduleIds; + QSet gateIds = pair.gateIds; + + ActionAddItemsToObject* act = new ActionAddItemsToObject(moduleIds,gateIds); + act->setObject(UserActionObject(id,UserActionObjectType::Context)); + UserActionManager::instance()->executeActionBlockThread(act); + return true; + } + + bool GuiApiClasses::View::removeFrom(int id, const std::vector modules, const std::vector gates) + { + //check if the inputs are valid + for(Module* mod : modules) + { + if (mod == nullptr) + { + log_warning("gui","Null values not allowed in module argument"); + return 0; + } + } + + for(Gate* gate : gates) + { + if (gate == nullptr) + { + log_warning("gui","Null values not allowed in gate argument"); + return 0; + } + } + + if (!gGraphContextManager->getContextById(id)) return false; // context does not exist + + QSet moduleIds; + QSet gateIds; + + for(Module* module : modules) + moduleIds.insert(module->get_id()); + for(Gate* gate : gates) + gateIds.insert(gate->get_id()); + + ActionRemoveItemsFromObject* act = new ActionRemoveItemsFromObject(moduleIds,gateIds); + act->setObject(UserActionObject(id,UserActionObjectType::Context)); + UserActionManager::instance()->executeActionBlockThread(act); + return true; + } + + bool GuiApiClasses::View::setName(int id, const std::string& name) + { + if (!gGraphContextManager->getContextById(id)) return false; // context does not exist + + //check if name is occupied + if(gGraphContextManager->contextWithNameExists(QString::fromStdString(name))) + return false; + + //check if view is exclusively bound to module + if(gGraphContextManager->getContextById(id)->isShowingModuleExclusively()) + return false; + + //get context matching id and rename it + ActionRenameObject* act = new ActionRenameObject(QString::fromStdString(name)); + act->setObject(UserActionObject(id,UserActionObjectType::Context)); + UserActionManager::instance()->executeActionBlockThread(act); + return true; + } + + int GuiApiClasses::View::getId(const std::string& name) + { + //check if there exists a context with the given name before we iterate over each of them + if(!gGraphContextManager->contextWithNameExists(QString::fromStdString(name))) + return 0; + + //find View related to the name + for(GraphContext* ctx : gGraphContextManager->getContexts()){ + if(ctx->name() == QString::fromStdString(name)){ + return ctx->id(); + } + } + return 0; + } + + std::string GuiApiClasses::View::getName(int id) + { + GraphContext* ctx = gGraphContextManager->getContextById(id); + if(ctx != nullptr){ + return ctx->name().toStdString(); + } + return {}; // + } + + std::vector GuiApiClasses::View::getModules(int id) + { + GraphContext* ctx = gGraphContextManager->getContextById(id); + if(ctx != nullptr){ + std::vector modules; + for(u32 id : ctx->modules()){ + modules.push_back(gNetlist->get_module_by_id(id)); + } + return modules; + } + return {}; + } + + std::vector GuiApiClasses::View::getGates(int id) + { + GraphContext* ctx = gGraphContextManager->getContextById(id); + if(ctx != nullptr){ + std::vector gates; + for(u32 id : ctx->gates()){ + gates.push_back(gNetlist->get_gate_by_id(id)); + } + return gates; + } + return {}; + } + + std::vector GuiApiClasses::View::getIds(const std::vector modules, const std::vector gates) + { + //check if the inputs are valid + for(Module* mod : modules) + { + if (mod == nullptr) + { + log_warning("gui","Null values not allowed in module argument"); + return {}; + } + } + + for(Gate* gate : gates) + { + if (gate == nullptr) + { + log_warning("gui","Null values not allowed in gate argument"); + return {}; + } + } + + std::vector ids; + + QSet moduleIds; + QSet gateIds; + + //Get ids of given modules and gates + for(Module* module : modules) + { + if(module) + moduleIds.insert(module->get_id()); + } + for(Gate* gate : gates) + { + if(gate) + gateIds.insert(gate->get_id()); + } + + //iterate over each context and look if its showing modules + for(GraphContext* ctx : gGraphContextManager->getContexts()){ + bool isCandidate = true; + + //Check if modules are in ctx + for(u32 moduleId : moduleIds){ + if(ctx->modules().contains(moduleId)) + continue; + isCandidate = false; + break; + } + //Only check modules if ctx still a valid candidate + if(isCandidate){ + for(u32 gateId : gateIds){ + if(ctx->gates().contains(gateId)) + continue; + isCandidate = false; + break; + } + } + + //add it to ids if its a candidate + if(isCandidate) + ids.push_back(ctx->id()); + + } + return ids; + } + bool GuiApiClasses::View::unfoldModule(int view_id, Module *module) + { + if(!module) + { + log_warning("gui","module must not be null"); + return false; + } + GraphContext* context = gGraphContextManager->getContextById(view_id); + + //check if the inputs are valid + if(context == nullptr) return false; + + if(!context->modules().contains(module->get_id())) return false; + + ActionUnfoldModule *act = new ActionUnfoldModule(module->get_id()); + UserActionManager::instance()->executeActionBlockThread(act); + return true; + } + + bool GuiApiClasses::View::foldModule(int view_id, Module *module) + { + if(!module) + { + log_warning("gui","module must not be null"); + return false; + } + GraphContext* context = gGraphContextManager->getContextById(view_id); + + //check if the inputs are valid + if(context == nullptr) return false; + + //get gates and submodules that belong to the current module + std::vector submodules = module->get_submodules(); + std::vector gates = module->get_gates(); + + bool isValidToFold = false; + //check if the view contains gates and submodules of the current module, return false if not + for(Gate* gate : gates) + if(context->gates().contains(gate->get_id())) {isValidToFold = true; break;} + for(Module* submodule : submodules) + if(context->modules().contains(submodule->get_id())) {isValidToFold = true; break;} + if (isValidToFold) + { + ActionFoldModule *act = new ActionFoldModule(module->get_id()); + UserActionManager::instance()->executeActionBlockThread(act); + return true; + } + return false; + } + + GuiApiClasses::View::ModuleGateIdPair GuiApiClasses::View::getValidObjects(int viewId, const std::vector mods, const std::vector gats) + { + Module* topModule = gNetlist->get_top_module(); + //copy to prevent inplace operations + std::vector modules = mods; + std::vector gates = gats; + + //set to store already existing mods and gates + QSet existingModules; + QSet existingGates; + + QSet Parents; + QSet modIds; + QSet gatIds; + //0) if its not a new view we have to check and remove all parents which have to be placed + if (viewId) + { + //put topmodule into parents because we dont iterate over it here + //Parents.insert(topModule->get_id()); + std::vector validMods; + //Add parents from the view modules to Parents + for (Module* mod : GuiApiClasses::View::getModules(viewId)) + { + existingModules.insert(mod->get_id()); + validMods.push_back(mod); + Module* itr = mod->get_parent_module(); + while (itr != nullptr) + { + if (Parents.contains(itr->get_id())) + break; + Parents.insert(itr->get_id()); + itr = itr->get_parent_module(); + } + + } + //Add parents from gate to parents + for (Gate* gate : GuiApiClasses::View::getGates(viewId)) + { + existingGates.insert(gate->get_id()); + Module* itr = gate->get_module(); + while (itr != nullptr) + { + if (Parents.contains(itr->get_id())) + break; + Parents.insert(itr->get_id()); + itr = itr->get_parent_module(); + } + } + //Delete every parent from the list if submodule is in the view + for (Module* mod : modules) + { + if (Parents.contains(mod->get_id())) + continue; + validMods.push_back(mod); + } + modules = validMods; + } + //get ids of modules + for (Module* mod : modules) + { + modIds.insert(mod->get_id()); + } + //1) sort them by priority in DESCENDING order + std::sort(modules.begin(), modules.end(), [](const Module* a, const Module* b) -> bool + { + //sorting in DESCENDING order to check from lowest to highest priority + return a->get_submodule_depth() > b->get_submodule_depth(); + } + ); + //2) remove id if parent is in set + for(Module* mod : modules){ + //check if top module + if(mod == topModule){ + // only add the top module because it has highest priority + QSet temp; + temp.insert(topModule->get_id()); + modIds = temp; + break; + } + //check if parent is in current set and if so remove current mod + Module* iterator = mod->get_parent_module(); + while(iterator != nullptr){ + if(modIds.contains(iterator->get_id())) + { + // parent is already in the set so remove mod and stop traversing parent tree + modIds.remove(mod->get_id()); + break; + } + iterator = iterator->get_parent_module(); + } + } + //3) remove all gates which has its ancestor in modIds + //check ancestors until topmodule or found in modIds + for (Gate* gate : gates) + { + Module* itr = gate->get_module(); + bool shouldInsert = true; + while (itr != nullptr) + { + if (modIds.contains(itr->get_id())) + { + gatIds.remove(gate->get_id()); + shouldInsert = false; + break; + } + itr = itr->get_parent_module(); + } + if (shouldInsert) + gatIds.insert(gate->get_id()); + } + + //remove duplicates + if (viewId) + { + modIds -= existingModules; + gatIds -= existingGates; + } + //create struct to return module and gate ID pairs + GuiApiClasses::View::ModuleGateIdPair pair; + pair.moduleIds = modIds; + pair.gateIds = gatIds; + + return pair; + + } + + GridPlacement* GuiApiClasses::View::getGridPlacement(int viewId) + { + GraphContext* context = gGraphContextManager->getContextById(viewId); + if (context == nullptr) return new GridPlacement(); + GridPlacement* retval = new GridPlacement(); + QMap contextNodeMap = context->getLayouter()->nodeToPositionMap(); + for (auto it = contextNodeMap.begin(); it != contextNodeMap.end(); it++) + retval->insert(it.key(), it.value()); + return retval; + } + + bool GuiApiClasses::View::setGridPlacement(int viewId, GridPlacement *gp) + { + ActionMoveNode* act = new ActionMoveNode(viewId, gp); + act->exec(); + + return true; + } } diff --git a/plugins/gui/src/main_window/main_window.cpp b/plugins/gui/src/main_window/main_window.cpp index b33ac330575..2848fbc080d 100644 --- a/plugins/gui/src/main_window/main_window.cpp +++ b/plugins/gui/src/main_window/main_window.cpp @@ -27,6 +27,7 @@ #include "hal_core/netlist/event_system/event_log.h" #include "hal_core/netlist/gate.h" #include "hal_core/netlist/gate_library/gate_library_manager.h" +#include "hal_core/netlist/grouping.h" #include "hal_core/netlist/net.h" #include "hal_core/netlist/netlist.h" #include "hal_core/netlist/netlist_factory.h" @@ -134,15 +135,15 @@ namespace hal setLocale(QLocale(QLocale::English, QLocale::UnitedStates)); - mActionNew = new Action(this); - mActionOpenProject = new Action(this); - mActionImportNetlist = new Action(this); - mActionSave = new Action(this); - mActionSaveAs = new Action(this); - mActionExportProject = new Action(this); - mActionImportProject = new Action(this); - mActionGateLibraryManager = new Action(this); - mActionAbout = new Action(this); + mActionNew = new Action(this); + mActionOpenProject = new Action(this); + mActionImportNetlist = new Action(this); + mActionSave = new Action(this); + mActionSaveAs = new Action(this); + mActionExportProject = new Action(this); + mActionImportProject = new Action(this); + // mActionGateLibraryManager = new Action(this); + mActionAbout = new Action(this); mActionStartRecording = new Action(this); mActionStopRecording = new Action(this); @@ -187,7 +188,7 @@ namespace hal mActionSaveAs->setIcon(gui_utility::getStyledSvgIcon(mSaveAsIconStyle, mSaveAsIconPath)); mActionClose->setIcon(gui_utility::getStyledSvgIcon(mCloseIconStyle, mCloseIconPath)); mActionQuit->setIcon(gui_utility::getStyledSvgIcon(mQuitIconStyle, mQuitIconPath)); - mActionGateLibraryManager->setIcon(gui_utility::getStyledSvgIcon(mSaveAsIconStyle, mSaveAsIconPath)); + // mActionGateLibraryManager->setIcon(gui_utility::getStyledSvgIcon(mSaveAsIconStyle, mSaveAsIconPath)); mActionUndo->setIcon(gui_utility::getStyledSvgIcon(mUndoIconStyle, mUndoIconPath)); mActionSettings->setIcon(gui_utility::getStyledSvgIcon(mSettingsIconStyle, mSettingsIconPath)); mActionPlugins->setIcon(gui_utility::getStyledSvgIcon(mPluginsIconStyle, mPluginsIconPath)); @@ -206,7 +207,7 @@ namespace hal mMenuFile->addAction(mActionClose); mMenuFile->addAction(mActionSave); mMenuFile->addAction(mActionSaveAs); - mMenuFile->addAction(mActionGateLibraryManager); + // mMenuFile->addAction(mActionGateLibraryManager); QMenu* menuImport = new QMenu("Import …", this); menuImport->addAction(mActionImportNetlist); @@ -286,7 +287,7 @@ namespace hal mActionImportNetlist->setText("Import Netlist"); mActionImportProject->setText("Import Project"); mActionExportProject->setText("Export Project"); - mActionGateLibraryManager->setText("Gate Library Manager"); + // mActionGateLibraryManager->setText("Gate Library Manager"); mActionUndo->setText("Undo"); mActionAbout->setText("About"); mActionSettings->setText("Settings"); @@ -339,7 +340,7 @@ namespace hal connect(mActionSaveAs, &Action::triggered, this, &MainWindow::handleSaveAsTriggered); connect(mActionExportProject, &Action::triggered, this, &MainWindow::handleExportProjectTriggered); connect(mActionImportProject, &Action::triggered, this, &MainWindow::handleImportProjectTriggered); - connect(mActionGateLibraryManager, &Action::triggered, this, &MainWindow::handleActionGatelibraryManager); + // connect(mActionGateLibraryManager, &Action::triggered, this, &MainWindow::handleActionGatelibraryManager); connect(mActionClose, &Action::triggered, this, &MainWindow::handleActionCloseFile); connect(mActionQuit, &Action::triggered, this, &MainWindow::onActionQuitTriggered); @@ -788,7 +789,7 @@ namespace hal } gPythonContext->updateNetlist(); - mActionGateLibraryManager->setVisible(false); + // mActionGateLibraryManager->setVisible(false); } void MainWindow::handleActionExport() @@ -817,7 +818,7 @@ namespace hal { if (ipd.importProject()) { - ActionOpenNetlistFile* act = new ActionOpenNetlistFile(ActionOpenNetlistFile::OpenProject, ipd.extractedProjectDir()); + ActionOpenNetlistFile* act = new ActionOpenNetlistFile(ActionOpenNetlistFile::OpenProject, ipd.extractedProjectAbsolutePath()); act->exec(); } else @@ -1060,7 +1061,7 @@ namespace hal gNetlistRelay->reset(); - mActionGateLibraryManager->setVisible(true); + // mActionGateLibraryManager->setVisible(true); return true; } @@ -1079,4 +1080,5 @@ namespace hal { SettingsManager::instance()->mainWindowSaveGeometry(pos(), size()); } + } // namespace hal diff --git a/plugins/gui/src/main_window/plugin_parameter_dialog.cpp b/plugins/gui/src/main_window/plugin_parameter_dialog.cpp index 08d30b7e00e..b5c9c05544d 100644 --- a/plugins/gui/src/main_window/plugin_parameter_dialog.cpp +++ b/plugins/gui/src/main_window/plugin_parameter_dialog.cpp @@ -1,6 +1,8 @@ #include "gui/main_window/plugin_parameter_dialog.h" #include "hal_core/plugin_system/plugin_interface_base.h" #include "hal_core/plugin_system/gui_extension_interface.h" +#include "gui/module_dialog/module_dialog.h" +#include "gui/module_dialog/gate_dialog.h" #include "gui/main_window/color_selection.h" #include "gui/main_window/key_value_table.h" #include "gui/gui_utils/graphics.h" @@ -17,6 +19,7 @@ #include #include #include +#include namespace hal { PluginParameterDialog::PluginParameterDialog(const QString &pname, GuiExtensionInterface *geif, QWidget* parent) @@ -140,6 +143,20 @@ namespace hal { case PluginParameter::NewFile: mWidgetMap[parTagname] = new PluginParameterFileDialog(par,this); break; + case PluginParameter::Module: + mWidgetMap[parTagname] = new PluginParameterNodeDialog(par,this); + break; + case PluginParameter::Gate: + mWidgetMap[parTagname] = new PluginParameterNodeDialog(par,this); + break; + case PluginParameter::ComboBox: + { + QComboBox* cbox = new QComboBox(this); + cbox->insertItems(0,QString::fromStdString(par.get_value()).split(';')); + if (!par.get_value().empty()) cbox->setCurrentIndex(0); + mWidgetMap[parTagname] = cbox; + break; + } default: break; } @@ -242,6 +259,19 @@ namespace hal { par.set_value(fileDlg->getFilename().toStdString()); break; } + case PluginParameter::Gate: + case PluginParameter::Module: + { + const PluginParameterNodeDialog* nodeDlg = static_cast(w); + par.set_value(QString::number(nodeDlg->getNodeId()).toStdString()); + break; + } + case PluginParameter::ComboBox: + { + const QComboBox* cbox = static_cast(w); + par.set_value(cbox->currentText().toStdString()); + break; + } default: continue; break; @@ -309,4 +339,106 @@ namespace hal { { return mEditor->text(); } + + PluginParameterNodeDialog::PluginParameterNodeDialog(const PluginParameter& par, QWidget* parent) + : QWidget(parent), mParameter(par) + { + QGridLayout* layout = new QGridLayout(this); + mNodeId = new QSpinBox(this); + mNodeId->setSizePolicy(QSizePolicy::Preferred,QSizePolicy::Preferred); + layout->addWidget(mNodeId,0,0); + mNodeName = new QLabel(this); + mNodeName->setSizePolicy(QSizePolicy::Expanding,QSizePolicy::Preferred); + mNodeName->setMinimumWidth(280); + layout->addWidget(mNodeName,0,1); + + mButton = new QPushButton("",this); + + u32 defaultId = QString::fromStdString(mParameter.get_value()).toUInt(); + mNodeId->setValue(defaultId); + + QString iconPath; + switch (mParameter.get_type()) { + case PluginParameter::Module: + iconPath = ":/icons/ne_module"; + setModule(defaultId); + connect(mButton,&QPushButton::clicked,this,&PluginParameterNodeDialog::handleActivateModuleDialog); + connect(mNodeId,static_cast(&QSpinBox::valueChanged),this,&PluginParameterNodeDialog::setModule); + mValidIds = gNetlist->get_used_module_ids(); + break; + case PluginParameter::Gate: + iconPath = ":/icons/ne_gate"; + setGate(defaultId); + connect(mButton,&QPushButton::clicked,this,&PluginParameterNodeDialog::handleActivateGateDialog); + connect(mNodeId,static_cast(&QSpinBox::valueChanged),this,&PluginParameterNodeDialog::setGate); + mValidIds = gNetlist->get_used_gate_ids(); + break; + default: + Q_ASSERT(1==0); // widget must not be created if parameter type not module or gate + break; + } + + u32 maxValue = 0; + for (u32 id : mValidIds) + if (id > maxValue) + maxValue = id; + mNodeId->setMaximum(maxValue); + if (defaultId > maxValue) + mNodeId->setValue(0); + mButton->setIcon(gui_utility::getStyledSvgIcon("all->#F0F0F1",iconPath)); + mButton->setSizePolicy(QSizePolicy::Fixed,QSizePolicy::Preferred); + layout->addWidget(mButton,0,2); + } + + void PluginParameterNodeDialog::setGate(int id) + { + if (!id || !isValidId(id)) + { + mNodeName->setText("(no gate selected)"); + return; + } + Gate* g = gNetlist->get_gate_by_id(id); + if (g) mNodeName->setText(QString::fromStdString(g->get_name())); + } + + void PluginParameterNodeDialog::setModule(int id) + { + if (!id || !isValidId(id)) + { + mNodeName->setText("(no module selected)"); + return; + } + Module* m = gNetlist->get_module_by_id(id); + if (m) mNodeName->setText(QString::fromStdString(m->get_name())); + } + + bool PluginParameterNodeDialog::isValidId(int id) const + { + return (mValidIds.find(id)!=mValidIds.end()); + } + + void PluginParameterNodeDialog::handleActivateModuleDialog() + { + ModuleDialog md({}, "Select module", nullptr, this); + if (md.exec() == QDialog::Accepted) + { + setModule(md.selectedId()); + mNodeId->setValue(md.selectedId()); + } + } + + void PluginParameterNodeDialog::handleActivateGateDialog() + { + GateDialog gd({}, "Select gate", nullptr, this); + if (gd.exec() == QDialog::Accepted) + { + setGate(gd.selectedId()); + mNodeId->setValue(gd.selectedId()); + } + } + + int PluginParameterNodeDialog::getNodeId() const + { + return mNodeId->value(); + } } diff --git a/plugins/gui/src/python/python_gui_api_bindings.cpp b/plugins/gui/src/python/python_gui_api_bindings.cpp index e65dd5fe924..7086ec44334 100644 --- a/plugins/gui/src/python/python_gui_api_bindings.cpp +++ b/plugins/gui/src/python/python_gui_api_bindings.cpp @@ -78,6 +78,159 @@ PYBIND11_PLUGIN(hal_gui) py::class_ py_gui_api(m, "GuiApi", R"(GUI API)"); + py::class_(py_gui_api,"GridPlacement",R"( + Helper class to determine placement of nodes on gui grid. + )") + + .def(py::init<>(), R"( + Constructor for empty placement hash. + )") + + .def("setGatePosition", &GridPlacement::setGatePosition, py::arg("gateId"), py::arg("point"), py::arg("swap") = false, R"( + Set position for gate identified by ID. + + :param int gateId: Gate ID. + :param tuple(int,int) pos: New position. + :param bool swap: set the swap of positions of the nodes + )") + + .def("setModulePosition", &GridPlacement::setModulePosition, py::arg("moduleId"), py::arg("point"), py::arg("swap") = false, R"( + Set position for module identified by ID. + + :param int moduleId: Module ID. + :param tuple(int,int) pos: New position. + :param bool swap: set the swap of positions of the nodes + )") + + .def("gatePosition", &GridPlacement::gatePosition, py::arg("gateId"), R"( + Query position for gate identified by ID. + + :param int gateId: Gate ID. + :returns: Position of gate or None if gate not found in hash. + :rtype: tuple(int,int) or None + )") + + .def("modulePosition", &GridPlacement::modulePosition, py::arg("moduleId"), R"( + Query position for module identified by ID. + + :param int moduleId: Module ID. + :returns: Position of module or None if module not found in hash. + :rtype: tuple(int,int) or None + )"); + + py::class_(py_gui_api, "View") + .def_static("isolateInNew", &GuiApiClasses::View::isolateInNew, py::arg("modules"), py::arg("gates"),R"( + Isolates given modules and gates into a new view + + :param list[hal_py.module] modules: List of modules to be added. + :param list[hal_py.Gate] gates: List of gates to be added. + :returns: ID of created view or the existing one if view is exclusively bound to a module. + :rtype: int +)") + .def_static("rename", &GuiApiClasses::View::setName, py::arg("id"), py::arg("name"),R"( + Renames the view specified by the given ID. + + :param int viewId: ID of the view. + :param string name: New unique name. + :returns: True on success otherwise False. + :rtype: bool +)") + .def_static("addTo", &GuiApiClasses::View::addTo, py::arg("id"), py::arg("modules"), py::arg("gates"),R"( + Adds the given modules and gates to the view specified by the ID. + + :param int viewId: ID of the view. + :param list[hal.py.module] modules: Modules to be added. + :param list[hal.py.Gate] gates: Gates to be added. + :returns: True on success, otherwise False. + :rtype: bool +)") + .def_static("deleteView", &GuiApiClasses::View::deleteView, py::arg("id"),R"( + Deletes the view specified by the ID. + + :param int viewId: ID of the view. + :returns: True on success, otherwise False. + :rtype: bool + )") + .def_static("removeFrom", &GuiApiClasses::View::removeFrom, py::arg("id"), py::arg("modules"), py::arg("gates"),R"( + Removes the given modules and gates from the view specified by the ID. + + :param int viewId: ID of the view. + :param list[hal.py.module] modules: Modules to be removed. + :param list[hal.py.Gate] gates: Gates to be removed. + :returns: True on success, otherwise False. + :rtype: bool +)") + .def_static("getId", &GuiApiClasses::View::getId, py::arg("name"),R"( + Returns the ID of the view with the given name if existing. + + :param string name: Name of the view. + :returns: ID of the specified view or 0 if none is found. + :rtype: int +)") + .def_static("getName", &GuiApiClasses::View::getName, py::arg("id"), R"( + Returns the name of the view with the given ID if existing. + + :param int viewId: ID of the view. + :returns: Name of the view specified by the ID or empty string if none is found. + :rtype: string +)") + .def_static("getModules", &GuiApiClasses::View::getModules, py::arg("id"), R"( + Returns all modules attached to the view. + + :param int viewId: ID of the view. + :returns: List of the attached modules. + :rtype: list[hal.py.module] +)") + .def_static("getGates", &GuiApiClasses::View::getGates, py::arg("id"),R"( + Returns all gates attached to the view + + :param int viewId: ID of the view. + :returns: List of the attached gates. + :rtype: list[hal.py.Gate] +)") + .def_static("getIds", &GuiApiClasses::View::getIds, py::arg("modules"), py::arg("gates"),R"( + Returns the ID of each View containing at least the given modules and gates. + + :param list[hal.py.module] modules: Required modules. + :param list[hal.py.Gate] gates: Required gates. + :returns: List of ID of views which contains modules and gates. + :rtype: list[int] +)") + .def_static("unfoldModule", &GuiApiClasses::View::unfoldModule, py::arg("view_id"), py::arg("module"), R"( + Unfold a specific module. Hides the module, shows submodules and gates + + :param int viewId: ID of the view. + :param Module* module: module to unfold + :returns: True on success, otherwise False. + :rtype: bool +)") + .def_static("foldModule", &GuiApiClasses::View::foldModule, py::arg("view_id"), py::arg("module"), R"( + Fold a specific module. Hides the submodules and gates, shows the specific module + + :param int viewId: ID of the view. + :param Module* module: module to fold + :returns: True on success, otherwise False. + :rtype: bool +)") + .def_static("getGridPlacement", &GuiApiClasses::View::getGridPlacement, py::arg("view_id"), R"( + Get positions of all nodes in the view specified by id + + :param int viewId: ID of the view. + :returns: GridPlacement of the specified view. + :rtype: GridPlacement +)") + + + .def_static("setGridPlacement", &GuiApiClasses::View::setGridPlacement, py::arg("view_id"), py::arg("grid placement"), R"( + Set grid placement to the view specified by id + + :param int viewId ID of the view. + :param GridPlacement* gp: grid placement. + :rtype: bool +)"); + + + py_gui_api.def("getSelectedGateIds", &GuiApi::getSelectedGateIds, R"( Get the gate ids of currently selected gates in the graph view of the GUI. @@ -466,6 +619,8 @@ PYBIND11_PLUGIN(hal_gui) Deselect all gates, nets and modules in the graph view of the GUI. )"); + + #ifndef PYBIND11_MODULE return m.ptr(); #endif // PYBIND11_MODULE diff --git a/plugins/gui/src/selection_details_widget/tree_navigation/selection_tree_view.cpp b/plugins/gui/src/selection_details_widget/tree_navigation/selection_tree_view.cpp index ec0d64d745f..cfc1959ca84 100644 --- a/plugins/gui/src/selection_details_widget/tree_navigation/selection_tree_view.cpp +++ b/plugins/gui/src/selection_details_widget/tree_navigation/selection_tree_view.cpp @@ -197,14 +197,7 @@ namespace hal if (nd.type() == Node::Gate) { - u32 cnt = 0; - for (;;) - { - ++cnt; - name = "Isolated View " + QString::number(cnt); - if (!gGraphContextManager->contextWithNameExists(name)) - break; - } + name = gGraphContextManager->nextViewName("Isolated View"); gateId.insert(nd.id()); } else if (nd.type() == Node::Module) diff --git a/plugins/gui/src/user_action/action_move_node.cpp b/plugins/gui/src/user_action/action_move_node.cpp index 673cf2b8189..ea927a4b123 100644 --- a/plugins/gui/src/user_action/action_move_node.cpp +++ b/plugins/gui/src/user_action/action_move_node.cpp @@ -2,6 +2,8 @@ #include #include "gui/graph_widget/contexts/graph_context.h" #include "gui/gui_globals.h" +#include "gui/implementations/qpoint_extension.h" +#include "hal_core/utilities/log.h" namespace hal { @@ -15,6 +17,69 @@ namespace hal return new ActionMoveNode; } + bool ActionMoveNode::checkContextId() + { + if (!mContextId) + { + log_warning("gui", "ActionMoveNode invoked without context ID."); + return false; + } + GraphContext* ctx = gGraphContextManager->getContextById(mContextId); + if (!ctx) + { + log_warning("gui", "ActionMoveNode invoked with illegal context ID {}.", mContextId); + mContextId = 0; + return false; + } + return true; + } + + ActionMoveNode::ActionMoveNode(u32 ctxId, const GridPlacement* gridPlc) + : mContextId(ctxId), mSwap(false) + { + if (!checkContextId()) return; + if (gridPlc) + mGridPlacement = *gridPlc; + } + + ActionMoveNode::ActionMoveNode(u32 ctxID, const QPoint& to) + : mContextId(ctxID), mTo(to), mSwap(false) + { + if (!checkContextId()) return; + } + + ActionMoveNode::ActionMoveNode(u32 ctxID, const QPoint& from, const QPoint& to, bool swap) + : mContextId(ctxID), mTo(to), mSwap(swap) + { + if (!checkContextId()) return; + GraphContext* ctx = gGraphContextManager->getContextById(mContextId); + auto it = ctx->getLayouter()->positionToNodeMap().find(from); + if (it == ctx->getLayouter()->positionToNodeMap().constEnd()) + { + mContextId = 0; // node not found, exit + return; + } + Node ndToMove = it.value(); // get the node we want to move + + if(mSwap) + { + + } + + switch (ndToMove.type()) + { + case Node::Module: + mObject = UserActionObject(ndToMove.id(),UserActionObjectType::Module); + break; + case Node::Gate: + mObject = UserActionObject(ndToMove.id(),UserActionObjectType::Gate); + break; + default: + mContextId = 0; // node type None, exit + return; + } + } + QString ActionMoveNode::tagname() const { return ActionMoveNodeFactory::sFactory->tagname(); @@ -22,13 +87,11 @@ namespace hal void ActionMoveNode::addToHash(QCryptographicHash& cryptoHash) const { - cryptoHash.addData((char*)(&mFrom), sizeof(QPoint)); cryptoHash.addData((char*)(&mTo) , sizeof(QPoint)); } void ActionMoveNode::writeToXml(QXmlStreamWriter& xmlOut) const { - xmlOut.writeTextElement("from", QString("%1,%2").arg(mFrom.x()).arg(mFrom.y())); xmlOut.writeTextElement("to", QString("%1,%2").arg(mTo.x()).arg(mTo.y())); } @@ -36,8 +99,6 @@ namespace hal { while (xmlIn.readNextStartElement()) { - if (xmlIn.name() == "from") - mFrom = parseFromString(xmlIn.readElementText()); if (xmlIn.name() == "to") mTo = parseFromString(xmlIn.readElementText()); } @@ -52,24 +113,48 @@ namespace hal bool ActionMoveNode::exec() { - GraphContext* ctx = mContextId >= 0 - ? gGraphContextManager->getContextById(mContextId) - : gGraphContextManager->getContextById(mObject.id()); - UserActionObject undoObject(mObject); + if (!mContextId) return false; + GraphContext* ctx = gGraphContextManager->getContextById(mContextId); if (!ctx) return false; - if (mContextId >= 0) + + // current placement for undo + ActionMoveNode* undo = new ActionMoveNode(mContextId, GuiApiClasses::View::getGridPlacement(mContextId)); + mUndoAction = undo; + + // test whether there is a user object + Node ndToMove; + switch (mObject.type()) { + case UserActionObjectType::Gate: + ndToMove = Node(mObject.id(),Node::Gate); + break; + case UserActionObjectType::Module: + ndToMove = Node(mObject.id(),Node::Module); + break; + default: + break; + } + if (ndToMove.type() != Node::None) { - Node nd(mObject.id(),UserActionObjectType::toNodeType(mObject.type())); - NodeBox* box = ctx->getLayouter()->boxes().boxForNode(nd); - if (!box) return false; - mFrom.setX(box->x()); - mFrom.setY(box->y()); - undoObject = UserActionObject(ctx->id(),UserActionObjectType::Context); + mGridPlacement = undo->mGridPlacement; + auto it = ctx->getLayouter()->positionToNodeMap().find(mTo); + if (it != ctx->getLayouter()->positionToNodeMap().constEnd()) + mGridPlacement[ndToMove] = mTo; } - ActionMoveNode* undo = new ActionMoveNode(mTo,mFrom); - undo->setObject(undoObject); - mUndoAction = undo; - ctx->moveNodeAction(mFrom,mTo); + + ctx->clear(); + + QSet modIds; + QSet gateIds; + + for (Node node : mGridPlacement.keys()) + { + if(node.isModule()) modIds.insert(node.id()); + else gateIds.insert(node.id()); + } + + ctx->add(modIds, gateIds, PlacementHint(mGridPlacement)); + ctx->scheduleSceneUpdate(); + return UserAction::exec(); } } diff --git a/plugins/gui/src/user_action/user_action_manager.cpp b/plugins/gui/src/user_action/user_action_manager.cpp index 654632b745e..8c5f90bb91a 100644 --- a/plugins/gui/src/user_action/user_action_manager.cpp +++ b/plugins/gui/src/user_action/user_action_manager.cpp @@ -21,7 +21,8 @@ namespace hal UserActionManager::UserActionManager(QObject *parent) : QObject(parent), mStartRecording(-1), mRecordHashAttribute(true), - mDumpAction(nullptr) + mDumpAction(nullptr), + mThreadedAction(nullptr) { mElapsedTime.start(); mSettingDumpAction = new SettingsItemCheckbox( @@ -32,6 +33,21 @@ namespace hal "Specifies whether hal opens an extra window to list all executed instances of UserAction" ); connect(mSettingDumpAction,&SettingsItemCheckbox::boolChanged,this,&UserActionManager::handleSettingDumpActionChanged); + connect(this,&UserActionManager::triggerExecute,this,&UserActionManager::handleTriggerExecute,Qt::BlockingQueuedConnection); + } + + void UserActionManager::executeActionBlockThread(UserAction *act) + { + if (!act) return; + mMutex.lock(); + mThreadedAction = act; + Q_EMIT triggerExecute(); + mMutex.unlock(); + } + + void UserActionManager::handleTriggerExecute() + { + mThreadedAction->exec(); } void UserActionManager::handleSettingDumpActionChanged(bool wantDump) diff --git a/plugins/gui_extension_demo/python/python_bindings.cpp b/plugins/gui_extension_demo/python/python_bindings.cpp index ee96e1e7d3d..41ab6935f64 100644 --- a/plugins/gui_extension_demo/python/python_bindings.cpp +++ b/plugins/gui_extension_demo/python/python_bindings.cpp @@ -51,10 +51,13 @@ namespace hal .value("Absent", PluginParameter::Absent, R"(Indicate not used.)") .value("Boolean", PluginParameter::Boolean, R"('true' or 'false'.)") .value("Color", PluginParameter::Color, R"(Color value like '#ffe080'.)") + .value("ComboBox", PluginParameter::ComboBox, R"(Combo box to select string from semicolon separated input list.)") .value("Dictionary", PluginParameter::Dictionary, R"(Key value pairs (string).)") .value("ExistingDir", PluginParameter::ExistingDir, R"(Existing directory.)") .value("Float", PluginParameter::Float, R"(Floating point number.)") + .value("Gate", PluginParameter::Gate, R"(Gate ID.)") .value("Integer", PluginParameter::Integer, R"(Integer number.)") + .value("Module", PluginParameter::Gate, R"(Module ID.)") .value("NewFile", PluginParameter::NewFile, R"(New file name.)") .value("PushButton", PluginParameter::PushButton, R"(Push Button.)") .value("String", PluginParameter::String, R"(String value.)")