| /* | |
| * nextpnr -- Next Generation Place and Route | |
| * | |
| * Copyright (C) 2018 Miodrag Milanovic <miodrag@symbioticeda.com> | |
| * Copyright (C) 2018 Serge Bazanski <q3k@symbioticeda.com> | |
| * | |
| * Permission to use, copy, modify, and/or distribute this software for any | |
| * purpose with or without fee is hereby granted, provided that the above | |
| * copyright notice and this permission notice appear in all copies. | |
| * | |
| * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES | |
| * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF | |
| * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR | |
| * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES | |
| * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN | |
| * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF | |
| * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. | |
| * | |
| */ | |
| #include "designwidget.h" | |
| #include <QAction> | |
| #include <QApplication> | |
| #include <QGridLayout> | |
| #include <QLineEdit> | |
| #include <QMenu> | |
| #include <QSplitter> | |
| #include <QToolBar> | |
| #include <QTreeWidgetItem> | |
| #include "fpgaviewwidget.h" | |
| NEXTPNR_NAMESPACE_BEGIN | |
| TreeView::TreeView(QWidget *parent) : QTreeView(parent) {} | |
| TreeView::~TreeView() {} | |
| void TreeView::mouseMoveEvent(QMouseEvent *event) | |
| { | |
| QModelIndex index = indexAt(event->pos()); | |
| if (index != current) { | |
| current = index; | |
| Q_EMIT hoverIndexChanged(index); | |
| } | |
| QTreeView::mouseMoveEvent(event); | |
| } | |
| void TreeView::leaveEvent(QEvent *event) { Q_EMIT hoverIndexChanged(QModelIndex()); } | |
| DesignWidget::DesignWidget(QWidget *parent) : QWidget(parent), ctx(nullptr) | |
| { | |
| tabWidget = new QTabWidget(); | |
| // Add tree view | |
| for (int i = 0; i < 6; i++) { | |
| treeView[i] = new TreeView(); | |
| treeModel[i] = new TreeModel::Model(); | |
| treeView[i]->setModel(treeModel[i]); | |
| treeView[i]->setContextMenuPolicy(Qt::CustomContextMenu); | |
| treeView[i]->setSelectionMode(QAbstractItemView::ExtendedSelection); | |
| treeView[i]->viewport()->setMouseTracking(true); | |
| selectionModel[i] = nullptr; | |
| } | |
| tabWidget->addTab(treeView[0], "Bels"); | |
| tabWidget->addTab(treeView[1], "Wires"); | |
| tabWidget->addTab(treeView[2], "Pips"); | |
| tabWidget->addTab(treeView[3], "Cells"); | |
| tabWidget->addTab(treeView[4], "Nets"); | |
| tabWidget->addTab(treeView[5], "Groups"); | |
| // Add property view | |
| variantManager = new QtVariantPropertyManager(this); | |
| readOnlyManager = new QtVariantPropertyManager(this); | |
| groupManager = new QtGroupPropertyManager(this); | |
| variantFactory = new QtVariantEditorFactory(this); | |
| propertyEditor = new QtTreePropertyBrowser(this); | |
| propertyEditor->setFactoryForManager(variantManager, variantFactory); | |
| propertyEditor->setPropertiesWithoutValueMarked(true); | |
| propertyEditor->show(); | |
| propertyEditor->treeWidget()->setContextMenuPolicy(Qt::CustomContextMenu); | |
| propertyEditor->treeWidget()->setSelectionMode(QAbstractItemView::ExtendedSelection); | |
| propertyEditor->treeWidget()->viewport()->setMouseTracking(true); | |
| searchEdit = new QLineEdit(); | |
| searchEdit->setClearButtonEnabled(true); | |
| searchEdit->addAction(QIcon(":/icons/resources/zoom.png"), QLineEdit::LeadingPosition); | |
| searchEdit->setPlaceholderText("Search..."); | |
| connect(searchEdit, &QLineEdit::returnPressed, this, &DesignWidget::onSearchInserted); | |
| actionFirst = new QAction("", this); | |
| actionFirst->setIcon(QIcon(":/icons/resources/resultset_first.png")); | |
| actionFirst->setEnabled(false); | |
| connect(actionFirst, &QAction::triggered, this, [this] { | |
| history_ignore = true; | |
| history_index = 0; | |
| auto h = history.at(history_index); | |
| if (tabWidget->currentIndex() != h.first) { | |
| selectionModel[tabWidget->currentIndex()]->clearSelection(); | |
| tabWidget->setCurrentIndex(h.first); | |
| selectionModel[h.first]->setCurrentIndex(h.second, QItemSelectionModel::Select); | |
| } else { | |
| selectionModel[h.first]->setCurrentIndex(h.second, QItemSelectionModel::ClearAndSelect); | |
| } | |
| updateButtons(); | |
| }); | |
| actionPrev = new QAction("", this); | |
| actionPrev->setIcon(QIcon(":/icons/resources/resultset_previous.png")); | |
| actionPrev->setEnabled(false); | |
| connect(actionPrev, &QAction::triggered, this, [this] { | |
| history_ignore = true; | |
| history_index--; | |
| auto h = history.at(history_index); | |
| if (tabWidget->currentIndex() != h.first) { | |
| selectionModel[tabWidget->currentIndex()]->clearSelection(); | |
| tabWidget->setCurrentIndex(h.first); | |
| selectionModel[h.first]->setCurrentIndex(h.second, QItemSelectionModel::Select); | |
| } else { | |
| selectionModel[h.first]->setCurrentIndex(h.second, QItemSelectionModel::ClearAndSelect); | |
| } | |
| updateButtons(); | |
| }); | |
| actionNext = new QAction("", this); | |
| actionNext->setIcon(QIcon(":/icons/resources/resultset_next.png")); | |
| actionNext->setEnabled(false); | |
| connect(actionNext, &QAction::triggered, this, [this] { | |
| history_ignore = true; | |
| history_index++; | |
| auto h = history.at(history_index); | |
| if (tabWidget->currentIndex() != h.first) { | |
| selectionModel[tabWidget->currentIndex()]->clearSelection(); | |
| tabWidget->setCurrentIndex(h.first); | |
| selectionModel[h.first]->setCurrentIndex(h.second, QItemSelectionModel::Select); | |
| } else { | |
| selectionModel[h.first]->setCurrentIndex(h.second, QItemSelectionModel::ClearAndSelect); | |
| } | |
| updateButtons(); | |
| }); | |
| actionLast = new QAction("", this); | |
| actionLast->setIcon(QIcon(":/icons/resources/resultset_last.png")); | |
| actionLast->setEnabled(false); | |
| connect(actionLast, &QAction::triggered, this, [this] { | |
| history_ignore = true; | |
| history_index = int(history.size() - 1); | |
| auto h = history.at(history_index); | |
| if (tabWidget->currentIndex() != h.first) { | |
| selectionModel[tabWidget->currentIndex()]->clearSelection(); | |
| tabWidget->setCurrentIndex(h.first); | |
| selectionModel[h.first]->setCurrentIndex(h.second, QItemSelectionModel::Select); | |
| } else { | |
| selectionModel[h.first]->setCurrentIndex(h.second, QItemSelectionModel::ClearAndSelect); | |
| } | |
| updateButtons(); | |
| }); | |
| actionClear = new QAction("", this); | |
| actionClear->setIcon(QIcon(":/icons/resources/cross.png")); | |
| actionClear->setEnabled(true); | |
| connect(actionClear, &QAction::triggered, this, [this] { | |
| history_index = -1; | |
| history.clear(); | |
| int num = tabWidget->currentIndex(); | |
| if (selectionModel[num]->selectedIndexes().size() > 0) { | |
| QModelIndex index = selectionModel[num]->selectedIndexes().at(0); | |
| if (index.isValid()) { | |
| ElementType type = treeModel[num]->nodeFromIndex(index)->type(); | |
| if (type != ElementType::NONE) | |
| addToHistory(num, index); | |
| } | |
| } | |
| updateButtons(); | |
| }); | |
| QToolBar *toolbar = new QToolBar(); | |
| toolbar->addAction(actionFirst); | |
| toolbar->addAction(actionPrev); | |
| toolbar->addAction(actionNext); | |
| toolbar->addAction(actionLast); | |
| toolbar->addAction(actionClear); | |
| QWidget *topWidget = new QWidget(); | |
| QVBoxLayout *vbox1 = new QVBoxLayout(); | |
| topWidget->setLayout(vbox1); | |
| vbox1->setSpacing(5); | |
| vbox1->setContentsMargins(0, 0, 0, 0); | |
| vbox1->addWidget(searchEdit); | |
| vbox1->addWidget(tabWidget); | |
| QWidget *toolbarWidget = new QWidget(); | |
| QHBoxLayout *hbox = new QHBoxLayout; | |
| hbox->setAlignment(Qt::AlignCenter); | |
| toolbarWidget->setLayout(hbox); | |
| hbox->setSpacing(0); | |
| hbox->setContentsMargins(0, 0, 0, 0); | |
| hbox->addWidget(toolbar); | |
| QWidget *btmWidget = new QWidget(); | |
| QVBoxLayout *vbox2 = new QVBoxLayout(); | |
| btmWidget->setLayout(vbox2); | |
| vbox2->setSpacing(0); | |
| vbox2->setContentsMargins(0, 0, 0, 0); | |
| vbox2->addWidget(toolbarWidget); | |
| vbox2->addWidget(propertyEditor); | |
| QSplitter *splitter = new QSplitter(Qt::Vertical); | |
| splitter->addWidget(topWidget); | |
| splitter->addWidget(btmWidget); | |
| QGridLayout *mainLayout = new QGridLayout(); | |
| mainLayout->setSpacing(0); | |
| mainLayout->setContentsMargins(0, 0, 0, 0); | |
| mainLayout->addWidget(splitter); | |
| setLayout(mainLayout); | |
| // Connection | |
| connect(propertyEditor->treeWidget(), &QTreeWidget::customContextMenuRequested, this, | |
| &DesignWidget::prepareMenuProperty); | |
| connect(propertyEditor->treeWidget(), &QTreeWidget::itemDoubleClicked, this, &DesignWidget::onItemDoubleClicked); | |
| connect(propertyEditor, &QtTreePropertyBrowser::hoverPropertyChanged, this, &DesignWidget::onHoverPropertyChanged); | |
| for (int num = 0; num < 6; num++) { | |
| connect(treeView[num], &TreeView::customContextMenuRequested, | |
| [this, num](const QPoint &pos) { prepareMenuTree(num, pos); }); | |
| connect(treeView[num], &TreeView::doubleClicked, [this](const QModelIndex &index) { onDoubleClicked(index); }); | |
| connect(treeView[num], &TreeView::hoverIndexChanged, | |
| [this, num](QModelIndex index) { onHoverIndexChanged(num, index); }); | |
| selectionModel[num] = treeView[num]->selectionModel(); | |
| connect(selectionModel[num], &QItemSelectionModel::selectionChanged, | |
| [this, num](const QItemSelection &selected, const QItemSelection &deselected) { | |
| onSelectionChanged(num, selected, deselected); | |
| }); | |
| } | |
| history_index = -1; | |
| history_ignore = false; | |
| highlightColors[0] = QColor("#6495ed"); | |
| highlightColors[1] = QColor("#7fffd4"); | |
| highlightColors[2] = QColor("#98fb98"); | |
| highlightColors[3] = QColor("#ffd700"); | |
| highlightColors[4] = QColor("#cd5c5c"); | |
| highlightColors[5] = QColor("#fa8072"); | |
| highlightColors[6] = QColor("#ff69b4"); | |
| highlightColors[7] = QColor("#da70d6"); | |
| } | |
| DesignWidget::~DesignWidget() {} | |
| void DesignWidget::updateButtons() | |
| { | |
| int count = int(history.size()); | |
| actionFirst->setEnabled(history_index > 0); | |
| actionPrev->setEnabled(history_index > 0); | |
| actionNext->setEnabled(history_index < (count - 1)); | |
| actionLast->setEnabled(history_index < (count - 1)); | |
| } | |
| void DesignWidget::addToHistory(int tab, QModelIndex item) | |
| { | |
| if (!history_ignore) { | |
| int count = int(history.size()); | |
| for (int i = count - 1; i > history_index; i--) | |
| history.pop_back(); | |
| history.push_back(std::make_pair(tab, item)); | |
| history_index++; | |
| } | |
| history_ignore = false; | |
| updateButtons(); | |
| } | |
| void DesignWidget::newContext(Context *ctx) | |
| { | |
| if (!ctx) | |
| return; | |
| highlightSelected.clear(); | |
| history_ignore = false; | |
| history_index = -1; | |
| history.clear(); | |
| updateButtons(); | |
| highlightSelected.clear(); | |
| this->ctx = ctx; | |
| { | |
| std::lock_guard<std::mutex> lock_ui(ctx->ui_mutex); | |
| std::lock_guard<std::mutex> lock(ctx->mutex); | |
| { | |
| TreeModel::ElementXYRoot<BelId>::ElementMap belMap; | |
| for (const auto &bel : ctx->getBels()) { | |
| auto loc = ctx->getBelLocation(bel); | |
| belMap[std::pair<int, int>(loc.x, loc.y)].push_back(bel); | |
| } | |
| auto belGetter = [](Context *ctx, BelId id) { return ctx->getBelName(id); }; | |
| getTreeByElementType(ElementType::BEL) | |
| ->loadData(ctx, | |
| std::unique_ptr<TreeModel::ElementXYRoot<BelId>>( | |
| new TreeModel::ElementXYRoot<BelId>(ctx, belMap, belGetter, ElementType::BEL))); | |
| } | |
| { | |
| TreeModel::ElementXYRoot<WireId>::ElementMap wireMap; | |
| #ifdef ARCH_ICE40 | |
| for (int i = 0; i < ctx->chip_info->num_wires; i++) { | |
| const auto wire = &ctx->chip_info->wire_data[i]; | |
| WireId wireid; | |
| wireid.index = i; | |
| wireMap[std::pair<int, int>(wire->x, wire->y)].push_back(wireid); | |
| } | |
| #endif | |
| #ifdef ARCH_ECP5 | |
| for (const auto &wire : ctx->getWires()) { | |
| wireMap[std::pair<int, int>(wire.location.x, wire.location.y)].push_back(wire); | |
| } | |
| #endif | |
| auto wireGetter = [](Context *ctx, WireId id) { return ctx->getWireName(id); }; | |
| getTreeByElementType(ElementType::WIRE) | |
| ->loadData(ctx, | |
| std::unique_ptr<TreeModel::ElementXYRoot<WireId>>(new TreeModel::ElementXYRoot<WireId>( | |
| ctx, wireMap, wireGetter, ElementType::WIRE))); | |
| } | |
| { | |
| TreeModel::ElementXYRoot<PipId>::ElementMap pipMap; | |
| for (const auto &pip : ctx->getPips()) { | |
| auto loc = ctx->getPipLocation(pip); | |
| pipMap[std::pair<int, int>(loc.x, loc.y)].push_back(pip); | |
| } | |
| auto pipGetter = [](Context *ctx, PipId id) { return ctx->getPipName(id); }; | |
| getTreeByElementType(ElementType::PIP) | |
| ->loadData(ctx, | |
| std::unique_ptr<TreeModel::ElementXYRoot<PipId>>( | |
| new TreeModel::ElementXYRoot<PipId>(ctx, pipMap, pipGetter, ElementType::PIP))); | |
| } | |
| getTreeByElementType(ElementType::CELL) | |
| ->loadData(ctx, | |
| std::unique_ptr<TreeModel::IdStringList>(new TreeModel::IdStringList(ElementType::CELL))); | |
| getTreeByElementType(ElementType::NET) | |
| ->loadData(ctx, | |
| std::unique_ptr<TreeModel::IdStringList>(new TreeModel::IdStringList(ElementType::NET))); | |
| } | |
| updateTree(); | |
| } | |
| void DesignWidget::updateTree() | |
| { | |
| clearProperties(); | |
| QMap<TreeModel::Item *, int>::iterator i = highlightSelected.begin(); | |
| while (i != highlightSelected.end()) { | |
| QMap<TreeModel::Item *, int>::iterator prev = i; | |
| ++i; | |
| if (prev.key()->type() == ElementType::NET && ctx->nets.find(prev.key()->id()) == ctx->nets.end()) { | |
| highlightSelected.erase(prev); | |
| } | |
| if (prev.key()->type() == ElementType::CELL && ctx->cells.find(prev.key()->id()) == ctx->cells.end()) { | |
| highlightSelected.erase(prev); | |
| } | |
| } | |
| { | |
| std::lock_guard<std::mutex> lock_ui(ctx->ui_mutex); | |
| std::lock_guard<std::mutex> lock(ctx->mutex); | |
| std::vector<IdString> cells; | |
| for (auto &pair : ctx->cells) { | |
| cells.push_back(pair.first); | |
| } | |
| std::vector<IdString> nets; | |
| for (auto &pair : ctx->nets) { | |
| nets.push_back(pair.first); | |
| } | |
| getTreeByElementType(ElementType::CELL)->updateElements(cells); | |
| getTreeByElementType(ElementType::NET)->updateElements(nets); | |
| } | |
| } | |
| QtProperty *DesignWidget::addTopLevelProperty(const QString &id) | |
| { | |
| QtProperty *topItem = groupManager->addProperty(id); | |
| propertyToId[topItem] = id; | |
| idToProperty[id] = topItem; | |
| topItem->setSelectable(false); | |
| propertyEditor->addProperty(topItem); | |
| return topItem; | |
| } | |
| void DesignWidget::clearProperties() | |
| { | |
| QMap<QtProperty *, QString>::ConstIterator itProp = propertyToId.constBegin(); | |
| while (itProp != propertyToId.constEnd()) { | |
| delete itProp.key(); | |
| itProp++; | |
| } | |
| propertyToId.clear(); | |
| idToProperty.clear(); | |
| } | |
| QString DesignWidget::getElementTypeName(ElementType type) | |
| { | |
| if (type == ElementType::NONE) | |
| return ""; | |
| if (type == ElementType::BEL) | |
| return "BEL"; | |
| if (type == ElementType::WIRE) | |
| return "WIRE"; | |
| if (type == ElementType::PIP) | |
| return "PIP"; | |
| if (type == ElementType::NET) | |
| return "NET"; | |
| if (type == ElementType::CELL) | |
| return "CELL"; | |
| return ""; | |
| } | |
| ElementType DesignWidget::getElementTypeByName(QString type) | |
| { | |
| if (type == "BEL") | |
| return ElementType::BEL; | |
| if (type == "WIRE") | |
| return ElementType::WIRE; | |
| if (type == "PIP") | |
| return ElementType::PIP; | |
| if (type == "NET") | |
| return ElementType::NET; | |
| if (type == "CELL") | |
| return ElementType::CELL; | |
| return ElementType::NONE; | |
| } | |
| TreeModel::Model *DesignWidget::getTreeByElementType(ElementType type) | |
| { | |
| if (type == ElementType::NONE) | |
| return nullptr; | |
| if (type == ElementType::BEL) | |
| return treeModel[0]; | |
| if (type == ElementType::WIRE) | |
| return treeModel[1]; | |
| if (type == ElementType::PIP) | |
| return treeModel[2]; | |
| if (type == ElementType::NET) | |
| return treeModel[3]; | |
| if (type == ElementType::CELL) | |
| return treeModel[4]; | |
| return nullptr; | |
| } | |
| int DesignWidget::getIndexByElementType(ElementType type) | |
| { | |
| if (type == ElementType::NONE) | |
| return -1; | |
| if (type == ElementType::BEL) | |
| return 0; | |
| if (type == ElementType::WIRE) | |
| return 1; | |
| if (type == ElementType::PIP) | |
| return 2; | |
| if (type == ElementType::NET) | |
| return 3; | |
| if (type == ElementType::CELL) | |
| return 4; | |
| if (type == ElementType::GROUP) | |
| return 5; | |
| return -1; | |
| } | |
| void DesignWidget::addProperty(QtProperty *topItem, int propertyType, const QString &name, QVariant value, | |
| const ElementType &type) | |
| { | |
| QtVariantProperty *item = readOnlyManager->addProperty(propertyType, name); | |
| item->setValue(value); | |
| item->setPropertyId(getElementTypeName(type)); | |
| item->setSelectable(type != ElementType::NONE); | |
| topItem->addSubProperty(item); | |
| } | |
| QtProperty *DesignWidget::addSubGroup(QtProperty *topItem, const QString &name) | |
| { | |
| QtProperty *item = groupManager->addProperty(name); | |
| item->setSelectable(false); | |
| topItem->addSubProperty(item); | |
| return item; | |
| } | |
| void DesignWidget::clearAllSelectionModels() | |
| { | |
| for (int i = 0; i <= getIndexByElementType(ElementType::GROUP); i++) | |
| selectionModel[i]->clearSelection(); | |
| } | |
| void DesignWidget::onClickedBel(BelId bel, bool keep) | |
| { | |
| boost::optional<TreeModel::Item *> item; | |
| { | |
| std::lock_guard<std::mutex> lock_ui(ctx->ui_mutex); | |
| std::lock_guard<std::mutex> lock(ctx->mutex); | |
| item = getTreeByElementType(ElementType::BEL)->nodeForId(ctx->getBelName(bel)); | |
| if (!item) | |
| return; | |
| Q_EMIT selected(getDecals(ElementType::BEL, ctx->getBelName(bel)), keep); | |
| } | |
| int index = getIndexByElementType(ElementType::BEL); | |
| if (!keep) | |
| clearAllSelectionModels(); | |
| if (tabWidget->currentIndex() != index) { | |
| tabWidget->setCurrentIndex(index); | |
| } | |
| selectionModel[index]->setCurrentIndex(getTreeByElementType(ElementType::BEL)->indexFromNode(*item), | |
| keep ? QItemSelectionModel::Select : QItemSelectionModel::ClearAndSelect); | |
| } | |
| void DesignWidget::onClickedWire(WireId wire, bool keep) | |
| { | |
| boost::optional<TreeModel::Item *> item; | |
| { | |
| std::lock_guard<std::mutex> lock_ui(ctx->ui_mutex); | |
| std::lock_guard<std::mutex> lock(ctx->mutex); | |
| item = getTreeByElementType(ElementType::WIRE)->nodeForId(ctx->getWireName(wire)); | |
| if (!item) | |
| return; | |
| Q_EMIT selected(getDecals(ElementType::WIRE, ctx->getWireName(wire)), keep); | |
| } | |
| int index = getIndexByElementType(ElementType::WIRE); | |
| if (!keep) | |
| clearAllSelectionModels(); | |
| if (tabWidget->currentIndex() != index) | |
| tabWidget->setCurrentIndex(index); | |
| selectionModel[index]->setCurrentIndex(getTreeByElementType(ElementType::WIRE)->indexFromNode(*item), | |
| keep ? QItemSelectionModel::Select : QItemSelectionModel::ClearAndSelect); | |
| } | |
| void DesignWidget::onClickedPip(PipId pip, bool keep) | |
| { | |
| boost::optional<TreeModel::Item *> item; | |
| { | |
| std::lock_guard<std::mutex> lock_ui(ctx->ui_mutex); | |
| std::lock_guard<std::mutex> lock(ctx->mutex); | |
| item = getTreeByElementType(ElementType::PIP)->nodeForId(ctx->getPipName(pip)); | |
| if (!item) | |
| return; | |
| Q_EMIT selected(getDecals(ElementType::PIP, ctx->getPipName(pip)), keep); | |
| } | |
| int index = getIndexByElementType(ElementType::PIP); | |
| if (!keep) | |
| clearAllSelectionModels(); | |
| if (tabWidget->currentIndex() != index) | |
| tabWidget->setCurrentIndex(index); | |
| selectionModel[index]->setCurrentIndex(getTreeByElementType(ElementType::PIP)->indexFromNode(*item), | |
| keep ? QItemSelectionModel::Select : QItemSelectionModel::ClearAndSelect); | |
| } | |
| void DesignWidget::onSelectionChanged(int num, const QItemSelection &, const QItemSelection &) | |
| { | |
| int num_selected = 0; | |
| std::vector<DecalXY> decals; | |
| for (int i = 0; i <= getIndexByElementType(ElementType::GROUP); i++) { | |
| num_selected += selectionModel[i]->selectedIndexes().size(); | |
| for (auto index : selectionModel[i]->selectedIndexes()) { | |
| TreeModel::Item *item = treeModel[i]->nodeFromIndex(index); | |
| std::vector<DecalXY> d = getDecals(item->type(), item->id()); | |
| std::move(d.begin(), d.end(), std::back_inserter(decals)); | |
| } | |
| } | |
| // Keep other tree seleciton only if Control is pressed | |
| if (num_selected > 1 && QApplication::keyboardModifiers().testFlag(Qt::ControlModifier) == true) { | |
| Q_EMIT selected(decals, false); | |
| return; | |
| } | |
| // For deselect and multiple select just send all | |
| if (selectionModel[num]->selectedIndexes().size() != 1) { | |
| Q_EMIT selected(decals, false); | |
| return; | |
| } | |
| QModelIndex index = selectionModel[num]->selectedIndexes().at(0); | |
| if (!index.isValid()) | |
| return; | |
| TreeModel::Item *clickItem = treeModel[num]->nodeFromIndex(index); | |
| ElementType type = clickItem->type(); | |
| if (type == ElementType::NONE) | |
| return; | |
| // Clear other tab selections | |
| for (int i = 0; i <= getIndexByElementType(ElementType::GROUP); i++) | |
| if (i != num) | |
| selectionModel[i]->clearSelection(); | |
| addToHistory(num, index); | |
| clearProperties(); | |
| IdString c = clickItem->id(); | |
| Q_EMIT selected(getDecals(type, c), false); | |
| if (type == ElementType::BEL) { | |
| std::lock_guard<std::mutex> lock_ui(ctx->ui_mutex); | |
| std::lock_guard<std::mutex> lock(ctx->mutex); | |
| BelId bel = ctx->getBelByName(c); | |
| QtProperty *topItem = addTopLevelProperty("Bel"); | |
| addProperty(topItem, QVariant::String, "Name", c.c_str(ctx)); | |
| addProperty(topItem, QVariant::String, "Type", ctx->getBelType(bel).c_str(ctx)); | |
| addProperty(topItem, QVariant::Bool, "Available", ctx->checkBelAvail(bel)); | |
| addProperty(topItem, QVariant::String, "Bound Cell", ctx->nameOf(ctx->getBoundBelCell(bel)), ElementType::CELL); | |
| addProperty(topItem, QVariant::String, "Conflicting Cell", ctx->nameOf(ctx->getConflictingBelCell(bel)), | |
| ElementType::CELL); | |
| QtProperty *attrsItem = addSubGroup(topItem, "Attributes"); | |
| for (auto &item : ctx->getBelAttrs(bel)) { | |
| addProperty(attrsItem, QVariant::String, item.first.c_str(ctx), item.second.c_str()); | |
| } | |
| QtProperty *belpinsItem = addSubGroup(topItem, "Ports"); | |
| for (const auto &item : ctx->getBelPins(bel)) { | |
| QtProperty *portInfoItem = addSubGroup(belpinsItem, item.c_str(ctx)); | |
| addProperty(portInfoItem, QVariant::String, "Name", item.c_str(ctx)); | |
| addProperty(portInfoItem, QVariant::Int, "Type", int(ctx->getBelPinType(bel, item))); | |
| WireId wire = ctx->getBelPinWire(bel, item); | |
| addProperty(portInfoItem, QVariant::String, "Wire", ctx->getWireName(wire).c_str(ctx), ElementType::WIRE); | |
| } | |
| } else if (type == ElementType::WIRE) { | |
| std::lock_guard<std::mutex> lock_ui(ctx->ui_mutex); | |
| std::lock_guard<std::mutex> lock(ctx->mutex); | |
| WireId wire = ctx->getWireByName(c); | |
| QtProperty *topItem = addTopLevelProperty("Wire"); | |
| addProperty(topItem, QVariant::String, "Name", c.c_str(ctx)); | |
| addProperty(topItem, QVariant::String, "Type", ctx->getWireType(wire).c_str(ctx)); | |
| addProperty(topItem, QVariant::Bool, "Available", ctx->checkWireAvail(wire)); | |
| addProperty(topItem, QVariant::String, "Bound Net", ctx->nameOf(ctx->getBoundWireNet(wire)), ElementType::NET); | |
| addProperty(topItem, QVariant::String, "Conflicting Wire", | |
| ctx->getWireName(ctx->getConflictingWireWire(wire)).c_str(ctx), ElementType::WIRE); | |
| addProperty(topItem, QVariant::String, "Conflicting Net", ctx->nameOf(ctx->getConflictingWireNet(wire)), | |
| ElementType::NET); | |
| QtProperty *attrsItem = addSubGroup(topItem, "Attributes"); | |
| for (auto &item : ctx->getWireAttrs(wire)) { | |
| addProperty(attrsItem, QVariant::String, item.first.c_str(ctx), item.second.c_str()); | |
| } | |
| DelayInfo delay = ctx->getWireDelay(wire); | |
| QtProperty *delayItem = addSubGroup(topItem, "Delay"); | |
| addProperty(delayItem, QVariant::Double, "Min Raise", delay.minRaiseDelay()); | |
| addProperty(delayItem, QVariant::Double, "Max Raise", delay.maxRaiseDelay()); | |
| addProperty(delayItem, QVariant::Double, "Min Fall", delay.minFallDelay()); | |
| addProperty(delayItem, QVariant::Double, "Max Fall", delay.maxFallDelay()); | |
| QtProperty *belpinsItem = addSubGroup(topItem, "BelPins"); | |
| for (const auto &item : ctx->getWireBelPins(wire)) { | |
| QString belname = ""; | |
| if (item.bel != BelId()) | |
| belname = ctx->getBelName(item.bel).c_str(ctx); | |
| QString pinname = item.pin.c_str(ctx); | |
| QtProperty *dhItem = addSubGroup(belpinsItem, belname + "-" + pinname); | |
| addProperty(dhItem, QVariant::String, "Bel", belname, ElementType::BEL); | |
| addProperty(dhItem, QVariant::String, "PortPin", pinname); | |
| } | |
| int counter = 0; | |
| QtProperty *pipsDownItem = addSubGroup(topItem, "Pips Downhill"); | |
| for (const auto &item : ctx->getPipsDownhill(wire)) { | |
| addProperty(pipsDownItem, QVariant::String, "", ctx->getPipName(item).c_str(ctx), ElementType::PIP); | |
| counter++; | |
| if (counter == 50) { | |
| addProperty(pipsDownItem, QVariant::String, "Warning", "Too many items...", ElementType::NONE); | |
| break; | |
| } | |
| } | |
| counter = 0; | |
| QtProperty *pipsUpItem = addSubGroup(topItem, "Pips Uphill"); | |
| for (const auto &item : ctx->getPipsUphill(wire)) { | |
| addProperty(pipsUpItem, QVariant::String, "", ctx->getPipName(item).c_str(ctx), ElementType::PIP); | |
| counter++; | |
| if (counter == 50) { | |
| addProperty(pipsUpItem, QVariant::String, "Warning", "Too many items...", ElementType::NONE); | |
| break; | |
| } | |
| } | |
| } else if (type == ElementType::PIP) { | |
| std::lock_guard<std::mutex> lock_ui(ctx->ui_mutex); | |
| std::lock_guard<std::mutex> lock(ctx->mutex); | |
| PipId pip = ctx->getPipByName(c); | |
| QtProperty *topItem = addTopLevelProperty("Pip"); | |
| addProperty(topItem, QVariant::String, "Name", c.c_str(ctx)); | |
| addProperty(topItem, QVariant::String, "Type", ctx->getPipType(pip).c_str(ctx)); | |
| addProperty(topItem, QVariant::Bool, "Available", ctx->checkPipAvail(pip)); | |
| addProperty(topItem, QVariant::String, "Bound Net", ctx->nameOf(ctx->getBoundPipNet(pip)), ElementType::NET); | |
| addProperty(topItem, QVariant::String, "Conflicting Wire", | |
| ctx->getWireName(ctx->getConflictingPipWire(pip)).c_str(ctx), ElementType::WIRE); | |
| addProperty(topItem, QVariant::String, "Conflicting Net", ctx->nameOf(ctx->getConflictingPipNet(pip)), | |
| ElementType::NET); | |
| addProperty(topItem, QVariant::String, "Src Wire", ctx->getWireName(ctx->getPipSrcWire(pip)).c_str(ctx), | |
| ElementType::WIRE); | |
| addProperty(topItem, QVariant::String, "Dest Wire", ctx->getWireName(ctx->getPipDstWire(pip)).c_str(ctx), | |
| ElementType::WIRE); | |
| QtProperty *attrsItem = addSubGroup(topItem, "Attributes"); | |
| for (auto &item : ctx->getPipAttrs(pip)) { | |
| addProperty(attrsItem, QVariant::String, item.first.c_str(ctx), item.second.c_str()); | |
| } | |
| DelayInfo delay = ctx->getPipDelay(pip); | |
| QtProperty *delayItem = addSubGroup(topItem, "Delay"); | |
| addProperty(delayItem, QVariant::Double, "Min Raise", delay.minRaiseDelay()); | |
| addProperty(delayItem, QVariant::Double, "Max Raise", delay.maxRaiseDelay()); | |
| addProperty(delayItem, QVariant::Double, "Min Fall", delay.minFallDelay()); | |
| addProperty(delayItem, QVariant::Double, "Max Fall", delay.maxFallDelay()); | |
| } else if (type == ElementType::NET) { | |
| std::lock_guard<std::mutex> lock_ui(ctx->ui_mutex); | |
| std::lock_guard<std::mutex> lock(ctx->mutex); | |
| NetInfo *net = ctx->nets.at(c).get(); | |
| QtProperty *topItem = addTopLevelProperty("Net"); | |
| addProperty(topItem, QVariant::String, "Name", net->name.c_str(ctx)); | |
| QtProperty *driverItem = addSubGroup(topItem, "Driver"); | |
| addProperty(driverItem, QVariant::String, "Port", net->driver.port.c_str(ctx)); | |
| addProperty(driverItem, QVariant::Double, "Budget", net->driver.budget); | |
| if (net->driver.cell) | |
| addProperty(driverItem, QVariant::String, "Cell", net->driver.cell->name.c_str(ctx), ElementType::CELL); | |
| else | |
| addProperty(driverItem, QVariant::String, "Cell", "", ElementType::CELL); | |
| QtProperty *usersItem = addSubGroup(topItem, "Users"); | |
| for (auto &item : net->users) { | |
| QtProperty *portItem = addSubGroup(usersItem, item.port.c_str(ctx)); | |
| addProperty(portItem, QVariant::String, "Port", item.port.c_str(ctx)); | |
| addProperty(portItem, QVariant::Double, "Budget", item.budget); | |
| if (item.cell) | |
| addProperty(portItem, QVariant::String, "Cell", item.cell->name.c_str(ctx), ElementType::CELL); | |
| else | |
| addProperty(portItem, QVariant::String, "Cell", "", ElementType::CELL); | |
| } | |
| QtProperty *attrsItem = addSubGroup(topItem, "Attributes"); | |
| for (auto &item : net->attrs) { | |
| addProperty(attrsItem, QVariant::String, item.first.c_str(ctx), item.second.c_str()); | |
| } | |
| QtProperty *wiresItem = addSubGroup(topItem, "Wires"); | |
| for (auto &item : net->wires) { | |
| auto name = ctx->getWireName(item.first).c_str(ctx); | |
| QtProperty *wireItem = addSubGroup(wiresItem, name); | |
| addProperty(wireItem, QVariant::String, "Wire", name, ElementType::WIRE); | |
| if (item.second.pip != PipId()) | |
| addProperty(wireItem, QVariant::String, "Pip", ctx->getPipName(item.second.pip).c_str(ctx), | |
| ElementType::PIP); | |
| else | |
| addProperty(wireItem, QVariant::String, "Pip", "", ElementType::PIP); | |
| addProperty(wireItem, QVariant::Int, "Strength", (int)item.second.strength); | |
| } | |
| } else if (type == ElementType::CELL) { | |
| std::lock_guard<std::mutex> lock_ui(ctx->ui_mutex); | |
| std::lock_guard<std::mutex> lock(ctx->mutex); | |
| CellInfo *cell = ctx->cells.at(c).get(); | |
| QtProperty *topItem = addTopLevelProperty("Cell"); | |
| addProperty(topItem, QVariant::String, "Name", cell->name.c_str(ctx)); | |
| addProperty(topItem, QVariant::String, "Type", cell->type.c_str(ctx)); | |
| if (cell->bel != BelId()) | |
| addProperty(topItem, QVariant::String, "Bel", ctx->getBelName(cell->bel).c_str(ctx), ElementType::BEL); | |
| else | |
| addProperty(topItem, QVariant::String, "Bel", "", ElementType::BEL); | |
| addProperty(topItem, QVariant::Int, "Bel strength", int(cell->belStrength)); | |
| QtProperty *cellPortsItem = addSubGroup(topItem, "Ports"); | |
| for (auto &item : cell->ports) { | |
| PortInfo p = item.second; | |
| QtProperty *portInfoItem = addSubGroup(cellPortsItem, p.name.c_str(ctx)); | |
| addProperty(portInfoItem, QVariant::String, "Name", p.name.c_str(ctx)); | |
| addProperty(portInfoItem, QVariant::Int, "Type", int(p.type)); | |
| if (p.net) | |
| addProperty(portInfoItem, QVariant::String, "Net", p.net->name.c_str(ctx), ElementType::NET); | |
| else | |
| addProperty(portInfoItem, QVariant::String, "Net", "", ElementType::NET); | |
| } | |
| QtProperty *cellAttrItem = addSubGroup(topItem, "Attributes"); | |
| for (auto &item : cell->attrs) { | |
| addProperty(cellAttrItem, QVariant::String, item.first.c_str(ctx), item.second.c_str()); | |
| } | |
| QtProperty *cellParamsItem = addSubGroup(topItem, "Parameters"); | |
| for (auto &item : cell->params) { | |
| addProperty(cellParamsItem, QVariant::String, item.first.c_str(ctx), item.second.c_str()); | |
| } | |
| QtProperty *cellPinsItem = groupManager->addProperty("Pins"); | |
| topItem->addSubProperty(cellPinsItem); | |
| for (auto &item : cell->pins) { | |
| std::string cell_port = item.first.c_str(ctx); | |
| std::string bel_pin = item.second.c_str(ctx); | |
| QtProperty *pinGroupItem = addSubGroup(cellPortsItem, (cell_port + " -> " + bel_pin).c_str()); | |
| addProperty(pinGroupItem, QVariant::String, "Cell", cell_port.c_str(), ElementType::CELL); | |
| addProperty(pinGroupItem, QVariant::String, "Bel", bel_pin.c_str(), ElementType::BEL); | |
| } | |
| } | |
| } | |
| std::vector<DecalXY> DesignWidget::getDecals(ElementType type, IdString value) | |
| { | |
| std::vector<DecalXY> decals; | |
| switch (type) { | |
| case ElementType::BEL: { | |
| BelId bel = ctx->getBelByName(value); | |
| if (bel != BelId()) { | |
| decals.push_back(ctx->getBelDecal(bel)); | |
| } | |
| } break; | |
| case ElementType::WIRE: { | |
| WireId wire = ctx->getWireByName(value); | |
| if (wire != WireId()) { | |
| decals.push_back(ctx->getWireDecal(wire)); | |
| } | |
| } break; | |
| case ElementType::PIP: { | |
| PipId pip = ctx->getPipByName(value); | |
| if (pip != PipId()) { | |
| decals.push_back(ctx->getPipDecal(pip)); | |
| } | |
| } break; | |
| case ElementType::NET: { | |
| NetInfo *net = ctx->nets.at(value).get(); | |
| for (auto &item : net->wires) { | |
| decals.push_back(ctx->getWireDecal(item.first)); | |
| if (item.second.pip != PipId()) { | |
| decals.push_back(ctx->getPipDecal(item.second.pip)); | |
| } | |
| } | |
| } break; | |
| case ElementType::CELL: { | |
| CellInfo *cell = ctx->cells.at(value).get(); | |
| if (cell->bel != BelId()) { | |
| decals.push_back(ctx->getBelDecal(cell->bel)); | |
| } | |
| } break; | |
| default: | |
| break; | |
| } | |
| return decals; | |
| } | |
| void DesignWidget::updateHighlightGroup(QList<TreeModel::Item *> items, int group) | |
| { | |
| const bool shouldClear = items.size() == 1; | |
| for (auto item : items) { | |
| if (highlightSelected.contains(item)) { | |
| if (shouldClear && highlightSelected[item] == group) { | |
| highlightSelected.remove(item); | |
| } else | |
| highlightSelected[item] = group; | |
| } else | |
| highlightSelected.insert(item, group); | |
| } | |
| std::vector<DecalXY> decals[8]; | |
| for (auto it : highlightSelected.toStdMap()) { | |
| std::vector<DecalXY> d = getDecals(it.first->type(), it.first->id()); | |
| std::move(d.begin(), d.end(), std::back_inserter(decals[it.second])); | |
| } | |
| for (int i = 0; i < 8; i++) | |
| Q_EMIT highlight(decals[i], i); | |
| } | |
| void DesignWidget::prepareMenuProperty(const QPoint &pos) | |
| { | |
| QTreeWidget *tree = propertyEditor->treeWidget(); | |
| QList<TreeModel::Item *> items; | |
| for (auto itemContextMenu : tree->selectedItems()) { | |
| QtBrowserItem *browserItem = propertyEditor->itemToBrowserItem(itemContextMenu); | |
| if (!browserItem) | |
| continue; | |
| QtProperty *selectedProperty = browserItem->property(); | |
| ElementType type = getElementTypeByName(selectedProperty->propertyId()); | |
| if (type == ElementType::NONE) | |
| continue; | |
| IdString value = ctx->id(selectedProperty->valueText().toStdString()); | |
| auto node = getTreeByElementType(type)->nodeForId(value); | |
| if (!node) | |
| continue; | |
| items.append(*node); | |
| } | |
| int selectedIndex = -1; | |
| if (items.size() == 1) { | |
| TreeModel::Item *item = items.at(0); | |
| if (highlightSelected.contains(item)) | |
| selectedIndex = highlightSelected[item]; | |
| } | |
| QMenu menu(this); | |
| QAction *selectAction = new QAction("&Select", this); | |
| connect(selectAction, &QAction::triggered, this, [this, items] { | |
| std::vector<DecalXY> decals; | |
| for (auto clickItem : items) { | |
| std::vector<DecalXY> d = getDecals(clickItem->type(), clickItem->id()); | |
| std::move(d.begin(), d.end(), std::back_inserter(decals)); | |
| } | |
| Q_EMIT selected(decals, false); | |
| }); | |
| menu.addAction(selectAction); | |
| QMenu *subMenu = menu.addMenu("Highlight"); | |
| QActionGroup *group = new QActionGroup(this); | |
| group->setExclusive(true); | |
| for (int i = 0; i < 8; i++) { | |
| QPixmap pixmap(32, 32); | |
| pixmap.fill(QColor(highlightColors[i])); | |
| QAction *action = new QAction(QIcon(pixmap), ("Group " + std::to_string(i)).c_str(), this); | |
| action->setCheckable(true); | |
| subMenu->addAction(action); | |
| group->addAction(action); | |
| if (selectedIndex == i) | |
| action->setChecked(true); | |
| connect(action, &QAction::triggered, this, [this, i, items] { updateHighlightGroup(items, i); }); | |
| } | |
| menu.exec(tree->mapToGlobal(pos)); | |
| } | |
| void DesignWidget::prepareMenuTree(int num, const QPoint &pos) | |
| { | |
| int selectedIndex = -1; | |
| if (selectionModel[num]->selectedIndexes().size() == 0) | |
| return; | |
| QList<TreeModel::Item *> items; | |
| for (int i = 0; i <= getIndexByElementType(ElementType::GROUP); i++) { | |
| for (auto index : selectionModel[i]->selectedIndexes()) { | |
| TreeModel::Item *item = treeModel[i]->nodeFromIndex(index); | |
| items.append(item); | |
| } | |
| } | |
| if (items.size() == 1) { | |
| TreeModel::Item *item = items.at(0); | |
| if (highlightSelected.contains(item)) | |
| selectedIndex = highlightSelected[item]; | |
| } | |
| QMenu menu(this); | |
| QMenu *subMenu = menu.addMenu("Highlight"); | |
| QActionGroup *group = new QActionGroup(this); | |
| group->setExclusive(true); | |
| for (int i = 0; i < 8; i++) { | |
| QPixmap pixmap(32, 32); | |
| pixmap.fill(QColor(highlightColors[i])); | |
| QAction *action = new QAction(QIcon(pixmap), ("Group " + std::to_string(i)).c_str(), this); | |
| action->setCheckable(true); | |
| subMenu->addAction(action); | |
| group->addAction(action); | |
| if (selectedIndex == i) | |
| action->setChecked(true); | |
| connect(action, &QAction::triggered, this, [this, i, items] { updateHighlightGroup(items, i); }); | |
| } | |
| menu.exec(treeView[num]->mapToGlobal(pos)); | |
| } | |
| void DesignWidget::onItemDoubleClicked(QTreeWidgetItem *item, int column) | |
| { | |
| QtProperty *selectedProperty = propertyEditor->itemToBrowserItem(item)->property(); | |
| ElementType type = getElementTypeByName(selectedProperty->propertyId()); | |
| if (type == ElementType::NONE) | |
| return; | |
| auto it = getTreeByElementType(type)->nodeForId(ctx->id(selectedProperty->valueText().toStdString())); | |
| if (it) { | |
| int num = getIndexByElementType(type); | |
| clearAllSelectionModels(); | |
| if (tabWidget->currentIndex() != num) | |
| tabWidget->setCurrentIndex(num); | |
| selectionModel[num]->setCurrentIndex(getTreeByElementType(type)->indexFromNode(*it), | |
| QItemSelectionModel::ClearAndSelect); | |
| } | |
| } | |
| void DesignWidget::onDoubleClicked(const QModelIndex &index) { Q_EMIT zoomSelected(); } | |
| void DesignWidget::onSearchInserted() | |
| { | |
| if (currentSearch == searchEdit->text() && currentIndexTab == tabWidget->currentIndex()) { | |
| currentIndex++; | |
| if (currentIndex >= currentSearchIndexes.size()) | |
| currentIndex = 0; | |
| } else { | |
| std::lock_guard<std::mutex> lock_ui(ctx->ui_mutex); | |
| std::lock_guard<std::mutex> lock(ctx->mutex); | |
| currentSearch = searchEdit->text(); | |
| currentSearchIndexes = treeModel[tabWidget->currentIndex()]->search(searchEdit->text()); | |
| currentIndex = 0; | |
| currentIndexTab = tabWidget->currentIndex(); | |
| } | |
| if (currentSearchIndexes.size() > 0 && currentIndex < currentSearchIndexes.size()) | |
| selectionModel[tabWidget->currentIndex()]->setCurrentIndex(currentSearchIndexes.at(currentIndex), | |
| QItemSelectionModel::ClearAndSelect); | |
| } | |
| void DesignWidget::onHoverIndexChanged(int num, QModelIndex index) | |
| { | |
| if (index.isValid()) { | |
| TreeModel::Item *item = treeModel[num]->nodeFromIndex(index); | |
| if (item->type() != ElementType::NONE) { | |
| std::vector<DecalXY> decals = getDecals(item->type(), item->id()); | |
| if (decals.size() > 0) | |
| Q_EMIT hover(decals.at(0)); | |
| return; | |
| } | |
| } | |
| Q_EMIT hover(DecalXY()); | |
| } | |
| void DesignWidget::onHoverPropertyChanged(QtBrowserItem *item) | |
| { | |
| if (item != nullptr) { | |
| QtProperty *selectedProperty = item->property(); | |
| ElementType type = getElementTypeByName(selectedProperty->propertyId()); | |
| if (type != ElementType::NONE) { | |
| IdString value = ctx->id(selectedProperty->valueText().toStdString()); | |
| if (value != IdString()) { | |
| auto node = getTreeByElementType(type)->nodeForId(value); | |
| if (node) { | |
| std::vector<DecalXY> decals = getDecals((*node)->type(), (*node)->id()); | |
| if (decals.size() > 0) | |
| Q_EMIT hover(decals.at(0)); | |
| return; | |
| } | |
| } | |
| } | |
| } | |
| Q_EMIT hover(DecalXY()); | |
| } | |
| NEXTPNR_NAMESPACE_END |