/* * nextpnr -- Next Generation Place and Route * * Copyright (C) 2018 Miodrag Milanovic * Copyright (C) 2018 Serge Bazanski * * 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 #include #include #include #include #include #include #include #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 lock_ui(ctx->ui_mutex); std::lock_guard lock(ctx->mutex); { TreeModel::ElementXYRoot::ElementMap belMap; for (const auto &bel : ctx->getBels()) { auto loc = ctx->getBelLocation(bel); belMap[std::pair(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>( new TreeModel::ElementXYRoot(ctx, belMap, belGetter, ElementType::BEL))); } { TreeModel::ElementXYRoot::ElementMap wireMap; #ifdef ARCH_ICE40 for (int i = 0; i < int(ctx->chip_info->wire_data.size()); i++) { const auto wire = &ctx->chip_info->wire_data[i]; WireId wireid; wireid.index = i; wireMap[std::pair(wire->x, wire->y)].push_back(wireid); } #endif #ifdef ARCH_ECP5 for (const auto &wire : ctx->getWires()) { wireMap[std::pair(wire.location.x, wire.location.y)].push_back(wire); } #endif #ifdef ARCH_MACHXO2 for (const auto &wire : ctx->getWires()) { wireMap[std::pair(wire.location.x, wire.location.y)].push_back(wire); } #endif #ifdef ARCH_GOWIN for (const auto &wire : ctx->getWires()) { WireInfo wi = ctx->wire_info(wire); wireMap[std::pair(wi.x, wi.y)].push_back(wire); } #endif auto wireGetter = [](Context *ctx, WireId id) { return ctx->getWireName(id); }; getTreeByElementType(ElementType::WIRE) ->loadData(ctx, std::unique_ptr>(new TreeModel::ElementXYRoot( ctx, wireMap, wireGetter, ElementType::WIRE))); } { TreeModel::ElementXYRoot::ElementMap pipMap; for (const auto &pip : ctx->getPips()) { auto loc = ctx->getPipLocation(pip); pipMap[std::pair(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>( new TreeModel::ElementXYRoot(ctx, pipMap, pipGetter, ElementType::PIP))); } getTreeByElementType(ElementType::CELL) ->loadData(ctx, std::unique_ptr(new TreeModel::IdList(ElementType::CELL))); getTreeByElementType(ElementType::NET) ->loadData(ctx, std::unique_ptr(new TreeModel::IdList(ElementType::NET))); } updateTree(); } void DesignWidget::updateTree() { clearProperties(); QMap::iterator i = highlightSelected.begin(); while (i != highlightSelected.end()) { QMap::iterator prev = i; ++i; if (prev.key()->type() == ElementType::NET && ctx->nets.find(prev.key()->id()[0]) == ctx->nets.end()) { highlightSelected.erase(prev); } if (prev.key()->type() == ElementType::CELL && ctx->cells.find(prev.key()->id()[0]) == ctx->cells.end()) { highlightSelected.erase(prev); } } { std::lock_guard lock_ui(ctx->ui_mutex); std::lock_guard lock(ctx->mutex); std::vector cells; for (auto &pair : ctx->cells) { cells.push_back(IdStringList(pair.first)); } std::vector nets; for (auto &pair : ctx->nets) { nets.push_back(IdStringList(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::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::CELL) return treeModel[3]; if (type == ElementType::NET) 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::CELL) return 3; if (type == ElementType::NET) 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 item; { std::lock_guard lock_ui(ctx->ui_mutex); std::lock_guard 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 item; { std::lock_guard lock_ui(ctx->ui_mutex); std::lock_guard 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 item; { std::lock_guard lock_ui(ctx->ui_mutex); std::lock_guard 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 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 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(); IdStringList c = clickItem->id(); Q_EMIT selected(getDecals(type, c), false); if (type == ElementType::BEL) { std::lock_guard lock_ui(ctx->ui_mutex); std::lock_guard lock(ctx->mutex); BelId bel = ctx->getBelByName(c); QtProperty *topItem = addTopLevelProperty("Bel"); addProperty(topItem, QVariant::String, "Name", ctx->nameOfBel(bel)); 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->nameOfWire(wire), ElementType::WIRE); } } else if (type == ElementType::WIRE) { std::lock_guard lock_ui(ctx->ui_mutex); std::lock_guard lock(ctx->mutex); WireId wire = ctx->getWireByName(c); QtProperty *topItem = addTopLevelProperty("Wire"); addProperty(topItem, QVariant::String, "Name", ctx->nameOfWire(wire)); 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->nameOfWire(ctx->getConflictingWireWire(wire)), 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()); } DelayQuad delay = ctx->getWireDelay(wire); QtProperty *delayItem = addSubGroup(topItem, "Delay"); addProperty(delayItem, QVariant::Double, "Min Rise", delay.minRiseDelay()); addProperty(delayItem, QVariant::Double, "Max Rise", delay.maxRiseDelay()); 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->nameOfBel(item.bel); 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->nameOfPip(item), 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->nameOfPip(item), 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 lock_ui(ctx->ui_mutex); std::lock_guard lock(ctx->mutex); PipId pip = ctx->getPipByName(c); QtProperty *topItem = addTopLevelProperty("Pip"); addProperty(topItem, QVariant::String, "Name", ctx->nameOfPip(pip)); 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); if (ctx->getConflictingPipWire(pip) != WireId()) { addProperty(topItem, QVariant::String, "Conflicting Wire", ctx->nameOfWire(ctx->getConflictingPipWire(pip)), ElementType::WIRE); } else { addProperty(topItem, QVariant::String, "Conflicting Wire", "", ElementType::NONE); } addProperty(topItem, QVariant::String, "Conflicting Net", ctx->nameOf(ctx->getConflictingPipNet(pip)), ElementType::NET); addProperty(topItem, QVariant::String, "Src Wire", ctx->nameOfWire(ctx->getPipSrcWire(pip)), ElementType::WIRE); addProperty(topItem, QVariant::String, "Dest Wire", ctx->nameOfWire(ctx->getPipDstWire(pip)), 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()); } DelayQuad delay = ctx->getPipDelay(pip); QtProperty *delayItem = addSubGroup(topItem, "Delay"); addProperty(delayItem, QVariant::Double, "Min Rise", delay.minRiseDelay()); addProperty(delayItem, QVariant::Double, "Max Rise", delay.maxRiseDelay()); addProperty(delayItem, QVariant::Double, "Min Fall", delay.minFallDelay()); addProperty(delayItem, QVariant::Double, "Max Fall", delay.maxFallDelay()); } else if (type == ElementType::NET) { std::lock_guard lock_ui(ctx->ui_mutex); std::lock_guard lock(ctx->mutex); NetInfo *net = ctx->nets.at(c[0]).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.is_string ? item.second.as_string().c_str() : item.second.to_string().c_str()); } QtProperty *wiresItem = addSubGroup(topItem, "Wires"); for (auto &item : net->wires) { auto name = ctx->nameOfWire(item.first); QtProperty *wireItem = addSubGroup(wiresItem, name); addProperty(wireItem, QVariant::String, "Wire", name, ElementType::WIRE); if (item.second.pip != PipId()) addProperty(wireItem, QVariant::String, "Pip", ctx->nameOfPip(item.second.pip), 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 lock_ui(ctx->ui_mutex); std::lock_guard lock(ctx->mutex); CellInfo *cell = ctx->cells.at(c[0]).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->nameOfBel(cell->bel), 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.is_string ? item.second.as_string().c_str() : item.second.to_string().c_str()); } QtProperty *cellParamsItem = addSubGroup(topItem, "Parameters"); for (auto &item : cell->params) { addProperty(cellParamsItem, QVariant::String, item.first.c_str(ctx), item.second.is_string ? item.second.as_string().c_str() : item.second.to_string().c_str()); } } } std::vector DesignWidget::getDecals(ElementType type, IdStringList value) { std::vector 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[0]).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[0]).get(); if (cell->bel != BelId()) { decals.push_back(ctx->getBelDecal(cell->bel)); } } break; default: break; } return decals; } void DesignWidget::updateHighlightGroup(QList 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 decals[8]; for (auto it : highlightSelected.toStdMap()) { std::vector 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 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; IdStringList value = IdStringList::parse(ctx, 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 decals; for (auto clickItem : items) { std::vector 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 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; IdStringList value = IdStringList::parse(ctx, selectedProperty->valueText().toStdString()); auto it = getTreeByElementType(type)->nodeForId(value); 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 lock_ui(ctx->ui_mutex); std::lock_guard 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 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) { IdStringList value = IdStringList::parse(ctx, selectedProperty->valueText().toStdString()); if (value != IdStringList()) { auto node = getTreeByElementType(type)->nodeForId(value); if (node) { std::vector decals = getDecals((*node)->type(), (*node)->id()); if (decals.size() > 0) Q_EMIT hover(decals.at(0)); return; } } } } Q_EMIT hover(DecalXY()); } NEXTPNR_NAMESPACE_END