From 7b09a7402e45e2c1a2538b2f3565607ac75eccdd Mon Sep 17 00:00:00 2001 From: Miodrag Milanovic Date: Sat, 28 Jul 2018 15:44:00 +0200 Subject: Move all to tree model --- gui/designwidget.cc | 344 ++++++++++------------------------------------------ gui/designwidget.h | 33 ++--- gui/treemodel.cc | 297 +++++++++++++++++++++++++++++++++++++++++++++ gui/treemodel.h | 92 ++++++++++++++ 4 files changed, 463 insertions(+), 303 deletions(-) create mode 100644 gui/treemodel.cc create mode 100644 gui/treemodel.h diff --git a/gui/designwidget.cc b/gui/designwidget.cc index d55c84e9..b321aef1 100644 --- a/gui/designwidget.cc +++ b/gui/designwidget.cc @@ -30,48 +30,14 @@ NEXTPNR_NAMESPACE_BEGIN -class ElementTreeItem : public QTreeWidgetItem +DesignWidget::DesignWidget(QWidget *parent) : QWidget(parent), ctx(nullptr) { - public: - ElementTreeItem(ElementType t, QString str, QTreeWidgetItem *parent) - : QTreeWidgetItem(parent, QStringList(str)), type(t) - { - this->setFlags(this->flags() & ~Qt::ItemIsSelectable); - } - virtual ~ElementTreeItem(){}; - - ElementType getType() { return type; }; - - private: - ElementType type; -}; - -class IdStringTreeItem : public ElementTreeItem -{ - public: - IdStringTreeItem(IdString d, ElementType t, QString str, QTreeWidgetItem *parent) : ElementTreeItem(t, str, parent) - { - this->setFlags(this->flags() | Qt::ItemIsSelectable); - this->data = d; - } - virtual ~IdStringTreeItem(){}; - - IdString getData() { return this->data; }; - - private: - IdString data; -}; - -DesignWidget::DesignWidget(QWidget *parent) : QWidget(parent), ctx(nullptr), nets_root(nullptr), cells_root(nullptr) -{ - - treeWidget = new QTreeWidget(); - // Add tree view - treeWidget->setColumnCount(1); - treeWidget->setHeaderLabel("Items"); - treeWidget->setContextMenuPolicy(Qt::CustomContextMenu); - treeWidget->setSelectionMode(QAbstractItemView::ExtendedSelection); + treeView = new QTreeView(); + treeModel = new ContextTreeModel(); + treeView->setModel(treeModel); + treeView->setContextMenuPolicy(Qt::CustomContextMenu); + treeView->setSelectionMode(QAbstractItemView::ExtendedSelection); // Add property view variantManager = new QtVariantPropertyManager(this); @@ -96,7 +62,7 @@ DesignWidget::DesignWidget(QWidget *parent) : QWidget(parent), ctx(nullptr), net connect(actionFirst, &QAction::triggered, this, [this] { history_ignore = true; history_index = 0; - treeWidget->setCurrentItem(history.at(history_index)); + selectionModel->setCurrentIndex(history.at(history_index), QItemSelectionModel::ClearAndSelect); updateButtons(); }); @@ -106,7 +72,7 @@ DesignWidget::DesignWidget(QWidget *parent) : QWidget(parent), ctx(nullptr), net connect(actionPrev, &QAction::triggered, this, [this] { history_ignore = true; history_index--; - treeWidget->setCurrentItem(history.at(history_index)); + selectionModel->setCurrentIndex(history.at(history_index), QItemSelectionModel::ClearAndSelect); updateButtons(); }); @@ -116,7 +82,7 @@ DesignWidget::DesignWidget(QWidget *parent) : QWidget(parent), ctx(nullptr), net connect(actionNext, &QAction::triggered, this, [this] { history_ignore = true; history_index++; - treeWidget->setCurrentItem(history.at(history_index)); + selectionModel->setCurrentIndex(history.at(history_index), QItemSelectionModel::ClearAndSelect); updateButtons(); }); @@ -126,7 +92,7 @@ DesignWidget::DesignWidget(QWidget *parent) : QWidget(parent), ctx(nullptr), net connect(actionLast, &QAction::triggered, this, [this] { history_ignore = true; history_index = int(history.size() - 1); - treeWidget->setCurrentItem(history.at(history_index)); + selectionModel->setCurrentIndex(history.at(history_index), QItemSelectionModel::ClearAndSelect); updateButtons(); }); @@ -136,11 +102,11 @@ DesignWidget::DesignWidget(QWidget *parent) : QWidget(parent), ctx(nullptr), net connect(actionClear, &QAction::triggered, this, [this] { history_index = -1; history.clear(); - QTreeWidgetItem *clickItem = treeWidget->selectedItems().at(0); - if (clickItem->parent()) { - ElementType type = static_cast(clickItem)->getType(); + QModelIndex index = selectionModel->selectedIndexes().at(0); + if (index.isValid()) { + ElementType type = treeModel->nodeFromIndex(index)->type(); if (type != ElementType::NONE) - addToHistory(treeWidget->selectedItems().at(0)); + addToHistory(index); } updateButtons(); }); @@ -158,7 +124,7 @@ DesignWidget::DesignWidget(QWidget *parent) : QWidget(parent), ctx(nullptr), net vbox1->setSpacing(5); vbox1->setContentsMargins(0, 0, 0, 0); vbox1->addWidget(lineEdit); - vbox1->addWidget(treeWidget); + vbox1->addWidget(treeView); QWidget *toolbarWidget = new QWidget(); QHBoxLayout *hbox = new QHBoxLayout; @@ -192,8 +158,10 @@ DesignWidget::DesignWidget(QWidget *parent) : QWidget(parent), ctx(nullptr), net &DesignWidget::prepareMenuProperty); connect(propertyEditor->treeWidget(), &QTreeWidget::itemDoubleClicked, this, &DesignWidget::onItemDoubleClicked); - connect(treeWidget, SIGNAL(itemSelectionChanged()), SLOT(onItemSelectionChanged())); - connect(treeWidget, &QTreeWidget::customContextMenuRequested, this, &DesignWidget::prepareMenuTree); + selectionModel = treeView->selectionModel(); + connect(selectionModel, SIGNAL(selectionChanged(const QItemSelection &, const QItemSelection &)), + SLOT(onSelectionChanged(const QItemSelection &, const QItemSelection &))); + connect(treeView, &QTreeWidget::customContextMenuRequested, this, &DesignWidget::prepareMenuTree); history_index = -1; history_ignore = false; @@ -219,7 +187,7 @@ void DesignWidget::updateButtons() actionLast->setEnabled(history_index < (count - 1)); } -void DesignWidget::addToHistory(QTreeWidgetItem *item) +void DesignWidget::addToHistory(QModelIndex item) { if (!history_ignore) { int count = int(history.size()); @@ -234,130 +202,16 @@ void DesignWidget::addToHistory(QTreeWidgetItem *item) void DesignWidget::newContext(Context *ctx) { + highlightSelected.clear(); - treeWidget->clear(); - // reset pointers since they are not valid after clear - nets_root = nullptr; - cells_root = nullptr; history_ignore = false; history_index = -1; history.clear(); updateButtons(); - for (int i = 0; i < 6; i++) - nameToItem[i].clear(); - + highlightSelected.clear(); this->ctx = ctx; - - // Add bels to tree - QTreeWidgetItem *bel_root = new QTreeWidgetItem(treeWidget); - QMap bel_items; - bel_root->setText(0, "Bels"); - bel_root->setFlags(bel_root->flags() & ~Qt::ItemIsSelectable); - treeWidget->insertTopLevelItem(0, bel_root); - if (ctx) { - for (auto bel : ctx->getBels()) { - auto id = ctx->getBelName(bel); - QStringList items = QString(id.c_str(ctx)).split("/"); - QString name; - QTreeWidgetItem *parent = nullptr; - for (int i = 0; i < items.size(); i++) { - if (!name.isEmpty()) - name += "/"; - name += items.at(i); - if (!bel_items.contains(name)) { - if (i == items.size() - 1) - nameToItem[0].insert(name, new IdStringTreeItem(id, ElementType::BEL, items.at(i), parent)); - else - bel_items.insert(name, new ElementTreeItem(ElementType::NONE, items.at(i), parent)); - } - parent = bel_items[name]; - } - } - } - for (auto bel : bel_items.toStdMap()) { - bel_root->addChild(bel.second); - } - for (auto bel : nameToItem[0].toStdMap()) { - bel_root->addChild(bel.second); - } - - // Add wires to tree - QTreeWidgetItem *wire_root = new QTreeWidgetItem(treeWidget); - QMap wire_items; - wire_root->setText(0, "Wires"); - wire_root->setFlags(wire_root->flags() & ~Qt::ItemIsSelectable); - treeWidget->insertTopLevelItem(0, wire_root); - if (ctx) { - for (auto wire : ctx->getWires()) { - auto id = ctx->getWireName(wire); - QStringList items = QString(id.c_str(ctx)).split("/"); - QString name; - QTreeWidgetItem *parent = nullptr; - for (int i = 0; i < items.size(); i++) { - if (!name.isEmpty()) - name += "/"; - name += items.at(i); - if (!wire_items.contains(name)) { - if (i == items.size() - 1) - nameToItem[1].insert(name, new IdStringTreeItem(id, ElementType::WIRE, items.at(i), parent)); - else - wire_items.insert(name, new ElementTreeItem(ElementType::NONE, items.at(i), parent)); - } - parent = wire_items[name]; - } - } - } - for (auto wire : wire_items.toStdMap()) { - wire_root->addChild(wire.second); - } - for (auto wire : nameToItem[1].toStdMap()) { - wire_root->addChild(wire.second); - } - // Add pips to tree - QTreeWidgetItem *pip_root = new QTreeWidgetItem(treeWidget); - QMap pip_items; - pip_root->setText(0, "Pips"); - pip_root->setFlags(pip_root->flags() & ~Qt::ItemIsSelectable); - treeWidget->insertTopLevelItem(0, pip_root); -#ifndef ARCH_ECP5 - if (ctx) { - for (auto pip : ctx->getPips()) { - auto id = ctx->getPipName(pip); - QStringList items = QString(id.c_str(ctx)).split("/"); - QString name; - QTreeWidgetItem *parent = nullptr; - for (int i = 0; i < items.size(); i++) { - if (!name.isEmpty()) - name += "/"; - name += items.at(i); - if (!pip_items.contains(name)) { - if (i == items.size() - 1) - nameToItem[2].insert(name, new IdStringTreeItem(id, ElementType::PIP, items.at(i), parent)); - else - pip_items.insert(name, new ElementTreeItem(ElementType::NONE, items.at(i), parent)); - } - parent = pip_items[name]; - } - } - } - for (auto pip : pip_items.toStdMap()) { - pip_root->addChild(pip.second); - } - for (auto pip : nameToItem[2].toStdMap()) { - pip_root->addChild(pip.second); - } -#endif - - nets_root = new QTreeWidgetItem(treeWidget); - nets_root->setText(0, "Nets"); - nets_root->setFlags(nets_root->flags() & ~Qt::ItemIsSelectable); - treeWidget->insertTopLevelItem(0, nets_root); - - cells_root = new QTreeWidgetItem(treeWidget); - cells_root->setText(0, "Cells"); - cells_root->setFlags(cells_root->flags() & ~Qt::ItemIsSelectable); - treeWidget->insertTopLevelItem(0, cells_root); + treeModel->loadData(ctx); updateTree(); } @@ -369,59 +223,7 @@ void DesignWidget::updateTree() clearProperties(); - // treeWidget->setSortingEnabled(false); - - // Remove nets not existing any more - QMap::iterator i = nameToItem[3].begin(); - while (i != nameToItem[3].end()) { - QMap::iterator prev = i; - ++i; - if (ctx->nets.find(ctx->id(prev.key().toStdString())) == ctx->nets.end()) { - if (treeWidget->currentItem() == prev.value()) - treeWidget->setCurrentItem(nets_root); - if (highlightSelected.contains(prev.value())) - highlightSelected.remove(prev.value()); - delete prev.value(); - nameToItem[3].erase(prev); - } - } - // Add nets to tree - for (auto &item : ctx->nets) { - auto id = item.first; - QString name = QString(id.c_str(ctx)); - if (!nameToItem[3].contains(name)) { - IdStringTreeItem *newItem = new IdStringTreeItem(id, ElementType::NET, name, nullptr); - nets_root->addChild(newItem); - nameToItem[3].insert(name, newItem); - } - } - - // Remove cells not existing any more - i = nameToItem[4].begin(); - while (i != nameToItem[4].end()) { - QMap::iterator prev = i; - ++i; - if (ctx->cells.find(ctx->id(prev.key().toStdString())) == ctx->cells.end()) { - if (treeWidget->currentItem() == prev.value()) - treeWidget->setCurrentItem(cells_root); - if (highlightSelected.contains(prev.value())) - highlightSelected.remove(prev.value()); - delete prev.value(); - nameToItem[4].erase(prev); - } - } - // Add cells to tree - for (auto &item : ctx->cells) { - auto id = item.first; - QString name = QString(id.c_str(ctx)); - if (!nameToItem[4].contains(name)) { - IdStringTreeItem *newItem = new IdStringTreeItem(id, ElementType::CELL, name, nullptr); - cells_root->addChild(newItem); - nameToItem[4].insert(name, newItem); - } - } - // treeWidget->sortByColumn(0, Qt::AscendingOrder); - // treeWidget->setSortingEnabled(true); + treeModel->updateData(ctx); } QtProperty *DesignWidget::addTopLevelProperty(const QString &id) { @@ -460,21 +262,6 @@ QString DesignWidget::getElementTypeName(ElementType type) return "CELL"; return ""; } -int DesignWidget::getElementIndex(ElementType type) -{ - 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; - return -1; -} - ElementType DesignWidget::getElementTypeByName(QString type) { if (type == "BEL") @@ -510,59 +297,55 @@ QtProperty *DesignWidget::addSubGroup(QtProperty *topItem, const QString &name) void DesignWidget::onClickedBel(BelId bel, bool keep) { - QTreeWidgetItem *item = nameToItem[getElementIndex(ElementType::BEL)].value(ctx->getBelName(bel).c_str(ctx)); - treeWidget->setCurrentItem(item); + ContextTreeItem *item = treeModel->nodeForIdType(ElementType::BEL, ctx->getBelName(bel).c_str(ctx)); + selectionModel->setCurrentIndex(treeModel->indexFromNode(item), QItemSelectionModel::ClearAndSelect); Q_EMIT selected(getDecals(ElementType::BEL, ctx->getBelName(bel)), keep); } void DesignWidget::onClickedWire(WireId wire, bool keep) { - QTreeWidgetItem *item = nameToItem[getElementIndex(ElementType::WIRE)].value(ctx->getWireName(wire).c_str(ctx)); - treeWidget->setCurrentItem(item); + ContextTreeItem *item = treeModel->nodeForIdType(ElementType::WIRE, ctx->getWireName(wire).c_str(ctx)); + selectionModel->setCurrentIndex(treeModel->indexFromNode(item), QItemSelectionModel::ClearAndSelect); Q_EMIT selected(getDecals(ElementType::WIRE, ctx->getWireName(wire)), keep); } void DesignWidget::onClickedPip(PipId pip, bool keep) { - QTreeWidgetItem *item = nameToItem[getElementIndex(ElementType::PIP)].value(ctx->getPipName(pip).c_str(ctx)); - treeWidget->setCurrentItem(item); + ContextTreeItem *item = treeModel->nodeForIdType(ElementType::PIP, ctx->getPipName(pip).c_str(ctx)); + selectionModel->setCurrentIndex(treeModel->indexFromNode(item), QItemSelectionModel::ClearAndSelect); Q_EMIT selected(getDecals(ElementType::PIP, ctx->getPipName(pip)), keep); } -void DesignWidget::onItemSelectionChanged() +void DesignWidget::onSelectionChanged(const QItemSelection &, const QItemSelection &) { - if (treeWidget->selectedItems().size() == 0) + if (selectionModel->selectedIndexes().size() == 0) return; - if (treeWidget->selectedItems().size() > 1) { + if (selectionModel->selectedIndexes().size() > 1) { std::vector decals; - for (auto clickItem : treeWidget->selectedItems()) { - IdString value = static_cast(clickItem)->getData(); - ElementType type = static_cast(clickItem)->getType(); - std::vector d = getDecals(type, value); + for (auto index : selectionModel->selectedIndexes()) { + ContextTreeItem *item = treeModel->nodeFromIndex(index); + std::vector d = getDecals(item->type(), item->id()); std::move(d.begin(), d.end(), std::back_inserter(decals)); } Q_EMIT selected(decals, false); return; } - - QTreeWidgetItem *clickItem = treeWidget->selectedItems().at(0); - - if (!clickItem->parent()) + QModelIndex index = selectionModel->selectedIndexes().at(0); + if (!index.isValid()) return; + ContextTreeItem *clickItem = treeModel->nodeFromIndex(index); - ElementType type = static_cast(clickItem)->getType(); - if (type == ElementType::NONE) { + ElementType type = clickItem->type(); + if (type == ElementType::NONE) return; - } - std::vector decals; - addToHistory(clickItem); + addToHistory(index); clearProperties(); - IdString c = static_cast(clickItem)->getData(); + IdString c = clickItem->id(); Q_EMIT selected(getDecals(type, c), false); if (type == ElementType::BEL) { @@ -799,7 +582,7 @@ std::vector DesignWidget::getDecals(ElementType type, IdString value) return decals; } -void DesignWidget::updateHighlightGroup(QList items, int group) +void DesignWidget::updateHighlightGroup(QList items, int group) { const bool shouldClear = items.size() == 1; for (auto item : items) { @@ -814,9 +597,7 @@ void DesignWidget::updateHighlightGroup(QList items, int grou std::vector decals[8]; for (auto it : highlightSelected.toStdMap()) { - ElementType type = static_cast(it.first)->getType(); - IdString value = static_cast(it.first)->getData(); - std::vector d = getDecals(type, value); + 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++) @@ -826,7 +607,7 @@ void DesignWidget::updateHighlightGroup(QList items, int grou void DesignWidget::prepareMenuProperty(const QPoint &pos) { QTreeWidget *tree = propertyEditor->treeWidget(); - QList items; + QList items; for (auto itemContextMenu : tree->selectedItems()) { QtBrowserItem *browserItem = propertyEditor->itemToBrowserItem(itemContextMenu); if (!browserItem) @@ -836,11 +617,11 @@ void DesignWidget::prepareMenuProperty(const QPoint &pos) if (type == ElementType::NONE) continue; IdString value = ctx->id(selectedProperty->valueText().toStdString()); - items.append(nameToItem[getElementIndex(type)].value(value.c_str(ctx))); + items.append(treeModel->nodeForIdType(type, value.c_str(ctx))); } int selectedIndex = -1; if (items.size() == 1) { - QTreeWidgetItem *item = items.at(0); + ContextTreeItem *item = items.at(0); if (highlightSelected.contains(item)) selectedIndex = highlightSelected[item]; } @@ -850,9 +631,7 @@ void DesignWidget::prepareMenuProperty(const QPoint &pos) connect(selectAction, &QAction::triggered, this, [this, items] { std::vector decals; for (auto clickItem : items) { - IdString value = static_cast(clickItem)->getData(); - ElementType type = static_cast(clickItem)->getType(); - std::vector d = getDecals(type, value); + std::vector d = getDecals(clickItem->type(), clickItem->id()); std::move(d.begin(), d.end(), std::back_inserter(decals)); } Q_EMIT selected(decals, false); @@ -878,12 +657,18 @@ void DesignWidget::prepareMenuProperty(const QPoint &pos) void DesignWidget::prepareMenuTree(const QPoint &pos) { - if (treeWidget->selectedItems().size() == 0) - return; int selectedIndex = -1; - QList items = treeWidget->selectedItems(); - if (treeWidget->selectedItems().size() == 1) { - QTreeWidgetItem *item = treeWidget->selectedItems().at(0); + + if (selectionModel->selectedIndexes().size() == 0) + return; + + QList items; + for (auto index : selectionModel->selectedIndexes()) { + ContextTreeItem *item = treeModel->nodeFromIndex(index); + items.append(item); + } + if (items.size() == 1) { + ContextTreeItem *item = items.at(0); if (highlightSelected.contains(item)) selectedIndex = highlightSelected[item]; } @@ -902,17 +687,16 @@ void DesignWidget::prepareMenuTree(const QPoint &pos) action->setChecked(true); connect(action, &QAction::triggered, this, [this, i, items] { updateHighlightGroup(items, i); }); } - menu.exec(treeWidget->mapToGlobal(pos)); + menu.exec(treeView->mapToGlobal(pos)); } void DesignWidget::onItemDoubleClicked(QTreeWidgetItem *item, int column) { QtProperty *selectedProperty = propertyEditor->itemToBrowserItem(item)->property(); ElementType type = getElementTypeByName(selectedProperty->propertyId()); - QString value = selectedProperty->valueText(); - int index = getElementIndex(type); - if (type != ElementType::NONE && nameToItem[index].contains(value)) - treeWidget->setCurrentItem(nameToItem[index].value(value)); + ContextTreeItem *it = treeModel->nodeForIdType(type, selectedProperty->valueText()); + if (it) + selectionModel->setCurrentIndex(treeModel->indexFromNode(it), QItemSelectionModel::ClearAndSelect); } NEXTPNR_NAMESPACE_END diff --git a/gui/designwidget.h b/gui/designwidget.h index 60291cf3..bbd188cd 100644 --- a/gui/designwidget.h +++ b/gui/designwidget.h @@ -20,27 +20,17 @@ #ifndef DESIGNWIDGET_H #define DESIGNWIDGET_H -#include +#include #include #include "nextpnr.h" #include "qtgroupboxpropertybrowser.h" #include "qtpropertymanager.h" #include "qttreepropertybrowser.h" #include "qtvariantproperty.h" +#include "treemodel.h" NEXTPNR_NAMESPACE_BEGIN -enum class ElementType -{ - NONE, - BEL, - WIRE, - PIP, - NET, - CELL, - GROUP -}; - class DesignWidget : public QWidget { Q_OBJECT @@ -59,9 +49,9 @@ class DesignWidget : public QWidget ElementType getElementTypeByName(QString type); int getElementIndex(ElementType type); void updateButtons(); - void addToHistory(QTreeWidgetItem *item); + void addToHistory(QModelIndex item); std::vector getDecals(ElementType type, IdString value); - void updateHighlightGroup(QList item, int group); + void updateHighlightGroup(QList item, int group); Q_SIGNALS: void info(std::string text); void selected(std::vector decal, bool keep); @@ -70,7 +60,7 @@ class DesignWidget : public QWidget private Q_SLOTS: void prepareMenuProperty(const QPoint &pos); void prepareMenuTree(const QPoint &pos); - void onItemSelectionChanged(); + void onSelectionChanged(const QItemSelection &selected, const QItemSelection &deselected); void onItemDoubleClicked(QTreeWidgetItem *item, int column); public Q_SLOTS: void newContext(Context *ctx); @@ -82,8 +72,9 @@ class DesignWidget : public QWidget private: Context *ctx; - QTreeWidget *treeWidget; - + QTreeView *treeView; + QItemSelectionModel *selectionModel; + ContextTreeModel *treeModel; QtVariantPropertyManager *variantManager; QtVariantPropertyManager *readOnlyManager; QtGroupPropertyManager *groupManager; @@ -93,14 +84,10 @@ class DesignWidget : public QWidget QMap propertyToId; QMap idToProperty; - QMap nameToItem[6]; - std::vector history; + std::vector history; int history_index; bool history_ignore; - QTreeWidgetItem *nets_root; - QTreeWidgetItem *cells_root; - QAction *actionFirst; QAction *actionPrev; QAction *actionNext; @@ -108,7 +95,7 @@ class DesignWidget : public QWidget QAction *actionClear; QColor highlightColors[8]; - QMap highlightSelected; + QMap highlightSelected; }; NEXTPNR_NAMESPACE_END diff --git a/gui/treemodel.cc b/gui/treemodel.cc new file mode 100644 index 00000000..65a17a2f --- /dev/null +++ b/gui/treemodel.cc @@ -0,0 +1,297 @@ +/* + * nextpnr -- Next Generation Place and Route + * + * Copyright (C) 2018 Miodrag Milanovic + * + * 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 "treemodel.h" + +NEXTPNR_NAMESPACE_BEGIN + +ContextTreeItem::ContextTreeItem() { parentNode = nullptr; } + +ContextTreeItem::ContextTreeItem(QString name) + : parentNode(nullptr), itemId(IdString()), itemType(ElementType::NONE), itemName(name) +{ +} + +ContextTreeItem::ContextTreeItem(IdString id, ElementType type, QString name) + : parentNode(nullptr), itemId(id), itemType(type), itemName(name) +{ +} + +ContextTreeItem::~ContextTreeItem() +{ + if (parentNode) + parentNode->children.removeOne(this); + qDeleteAll(children); +} +void ContextTreeItem::addChild(ContextTreeItem *item) +{ + item->parentNode = this; + children.append(item); +} + +ContextTreeModel::ContextTreeModel(QObject *parent) : QAbstractItemModel(parent) { root = new ContextTreeItem(); } + +ContextTreeModel::~ContextTreeModel() { delete root; } + +void ContextTreeModel::loadData(Context *ctx) +{ + if (!ctx) + return; + delete root; + root = new ContextTreeItem(); + + for (int i = 0; i < 6; i++) + nameToItem[i].clear(); + + IdString none; + + ContextTreeItem *bels_root = new ContextTreeItem("Bels"); + root->addChild(bels_root); + QMap bel_items; + + // Add bels to tree + for (auto bel : ctx->getBels()) { + IdString id = ctx->getBelName(bel); + QStringList items = QString(id.c_str(ctx)).split("/"); + QString name; + ContextTreeItem *parent = bels_root; + for (int i = 0; i < items.size(); i++) { + if (!name.isEmpty()) + name += "/"; + name += items.at(i); + if (!bel_items.contains(name)) { + if (i == items.size() - 1) { + ContextTreeItem *item = new ContextTreeItem(id, ElementType::BEL, items.at(i)); + parent->addChild(item); + nameToItem[0].insert(name, item); + } else { + ContextTreeItem *item = new ContextTreeItem(none, ElementType::NONE, items.at(i)); + parent->addChild(item); + bel_items.insert(name, item); + } + } + parent = bel_items[name]; + } + } + + ContextTreeItem *wire_root = new ContextTreeItem("Wires"); + root->addChild(wire_root); + QMap wire_items; + + // Add wires to tree + for (auto wire : ctx->getWires()) { + auto id = ctx->getWireName(wire); + QStringList items = QString(id.c_str(ctx)).split("/"); + QString name; + ContextTreeItem *parent = wire_root; + for (int i = 0; i < items.size(); i++) { + if (!name.isEmpty()) + name += "/"; + name += items.at(i); + if (!wire_items.contains(name)) { + if (i == items.size() - 1) { + ContextTreeItem *item = new ContextTreeItem(id, ElementType::WIRE, items.at(i)); + parent->addChild(item); + nameToItem[1].insert(name, item); + } else { + ContextTreeItem *item = new ContextTreeItem(none, ElementType::NONE, items.at(i)); + parent->addChild(item); + wire_items.insert(name, item); + } + } + parent = wire_items[name]; + } + } + + ContextTreeItem *pip_root = new ContextTreeItem("Pips"); + root->addChild(pip_root); + QMap pip_items; + + // Add pips to tree + for (auto pip : ctx->getPips()) { + auto id = ctx->getPipName(pip); + QStringList items = QString(id.c_str(ctx)).split("/"); + QString name; + ContextTreeItem *parent = pip_root; + for (int i = 0; i < items.size(); i++) { + if (!name.isEmpty()) + name += "/"; + name += items.at(i); + if (!pip_items.contains(name)) { + if (i == items.size() - 1) { + ContextTreeItem *item = new ContextTreeItem(id, ElementType::PIP, items.at(i)); + parent->addChild(item); + nameToItem[2].insert(name, item); + } else { + ContextTreeItem *item = new ContextTreeItem(none, ElementType::NONE, items.at(i)); + parent->addChild(item); + pip_items.insert(name, item); + } + } + parent = pip_items[name]; + } + } + + nets_root = new ContextTreeItem("Nets"); + root->addChild(nets_root); + + cells_root = new ContextTreeItem("Cells"); + root->addChild(cells_root); +} + +void ContextTreeModel::updateData(Context *ctx) +{ + if (!ctx) + return; + + // Remove nets not existing any more + QMap::iterator i = nameToItem[3].begin(); + while (i != nameToItem[3].end()) { + QMap::iterator prev = i; + ++i; + if (ctx->nets.find(ctx->id(prev.key().toStdString())) == ctx->nets.end()) { + /* if (treeWidget->currentItem() == prev.value()) + treeWidget->setCurrentItem(nets_root); + if (highlightSelected.contains(prev.value())) + highlightSelected.remove(prev.value());*/ + delete prev.value(); + nameToItem[3].erase(prev); + } + } + // Add nets to tree + for (auto &item : ctx->nets) { + auto id = item.first; + QString name = QString(id.c_str(ctx)); + if (!nameToItem[3].contains(name)) { + ContextTreeItem *newItem = new ContextTreeItem(id, ElementType::NET, name); + nets_root->addChild(newItem); + nameToItem[3].insert(name, newItem); + } + } + + // Remove cells not existing any more + i = nameToItem[4].begin(); + while (i != nameToItem[4].end()) { + QMap::iterator prev = i; + ++i; + if (ctx->cells.find(ctx->id(prev.key().toStdString())) == ctx->cells.end()) { + /* if (treeWidget->currentItem() == prev.value()) + treeWidget->setCurrentItem(cells_root); + if (highlightSelected.contains(prev.value())) + highlightSelected.remove(prev.value());*/ + delete prev.value(); + nameToItem[4].erase(prev); + } + } + // Add cells to tree + for (auto &item : ctx->cells) { + auto id = item.first; + QString name = QString(id.c_str(ctx)); + if (!nameToItem[4].contains(name)) { + ContextTreeItem *newItem = new ContextTreeItem(id, ElementType::CELL, name); + cells_root->addChild(newItem); + nameToItem[4].insert(name, newItem); + } + } +} + +int ContextTreeModel::rowCount(const QModelIndex &parent) const { return nodeFromIndex(parent)->count(); } + +int ContextTreeModel::columnCount(const QModelIndex &parent) const { return 1; } + +QModelIndex ContextTreeModel::index(int row, int column, const QModelIndex &parent) const +{ + ContextTreeItem *node = nodeFromIndex(parent); + if (row >= node->count()) + return QModelIndex(); + return createIndex(row, column, node->at(row)); +} + +QModelIndex ContextTreeModel::parent(const QModelIndex &child) const +{ + ContextTreeItem *parent = nodeFromIndex(child)->parent(); + if (parent == root) + return QModelIndex(); + ContextTreeItem *node = parent->parent(); + return createIndex(node->indexOf(parent), 0, parent); +} + +QVariant ContextTreeModel::data(const QModelIndex &index, int role) const +{ + if (index.column() != 0) + return QVariant(); + if (role != Qt::DisplayRole) + return QVariant(); + ContextTreeItem *node = nodeFromIndex(index); + return node->name(); +} + +QVariant ContextTreeModel::headerData(int section, Qt::Orientation orientation, int role) const +{ + Q_UNUSED(section); + if (orientation == Qt::Horizontal && role == Qt::DisplayRole) + return QString("Items"); + + return QVariant(); +} + +ContextTreeItem *ContextTreeModel::nodeFromIndex(const QModelIndex &idx) const +{ + if (idx.isValid()) + return (ContextTreeItem *)idx.internalPointer(); + return root; +} + +static int getElementIndex(ElementType type) +{ + 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; + return -1; +} + +ContextTreeItem *ContextTreeModel::nodeForIdType(const ElementType type, const QString name) const +{ + int index = getElementIndex(type); + if (type != ElementType::NONE && nameToItem[index].contains(name)) + return nameToItem[index].value(name); + return nullptr; +} + +QModelIndex ContextTreeModel::indexFromNode(ContextTreeItem *node) +{ + ContextTreeItem *parent = node->parent(); + if (parent == root) + return QModelIndex(); + return createIndex(parent->indexOf(node), 0, node); +} + +Qt::ItemFlags ContextTreeModel::flags(const QModelIndex &index) const +{ + ContextTreeItem *node = nodeFromIndex(index); + return Qt::ItemIsEnabled | (node->type() != ElementType::NONE ? Qt::ItemIsSelectable : Qt::NoItemFlags); +} +NEXTPNR_NAMESPACE_END \ No newline at end of file diff --git a/gui/treemodel.h b/gui/treemodel.h new file mode 100644 index 00000000..a976c5bc --- /dev/null +++ b/gui/treemodel.h @@ -0,0 +1,92 @@ +/* + * nextpnr -- Next Generation Place and Route + * + * Copyright (C) 2018 Miodrag Milanovic + * + * 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. + * + */ + +#ifndef TREEMODEL_H +#define TREEMODEL_H + +#include +#include "nextpnr.h" + +NEXTPNR_NAMESPACE_BEGIN + +enum class ElementType +{ + NONE, + BEL, + WIRE, + PIP, + NET, + CELL, + GROUP +}; + +class ContextTreeItem +{ + public: + ContextTreeItem(); + ContextTreeItem(QString name); + ContextTreeItem(IdString id, ElementType type, QString name); + ~ContextTreeItem(); + + void addChild(ContextTreeItem *item); + int indexOf(ContextTreeItem *n) const { return children.indexOf(n); } + ContextTreeItem *at(int idx) const { return children.at(idx); } + int count() const { return children.count(); } + ContextTreeItem *parent() const { return parentNode; } + IdString id() const { return itemId; } + ElementType type() const { return itemType; } + QString name() const { return itemName; } + private: + ContextTreeItem *parentNode; + QList children; + IdString itemId; + ElementType itemType; + QString itemName; +}; + +class ContextTreeModel : public QAbstractItemModel +{ + public: + ContextTreeModel(QObject *parent = nullptr); + ~ContextTreeModel(); + + void loadData(Context *ctx); + void updateData(Context *ctx); + ContextTreeItem *nodeFromIndex(const QModelIndex &idx) const; + QModelIndex indexFromNode(ContextTreeItem *node); + ContextTreeItem *nodeForIdType(const ElementType type, const QString name) const; + // Override QAbstractItemModel methods + int rowCount(const QModelIndex &parent = QModelIndex()) const Q_DECL_OVERRIDE; + int columnCount(const QModelIndex &parent = QModelIndex()) const Q_DECL_OVERRIDE; + QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const Q_DECL_OVERRIDE; + QModelIndex parent(const QModelIndex &child) const Q_DECL_OVERRIDE; + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const Q_DECL_OVERRIDE; + QVariant headerData(int section, Qt::Orientation orientation, int role) const Q_DECL_OVERRIDE; + Qt::ItemFlags flags(const QModelIndex &index) const Q_DECL_OVERRIDE; + + private: + ContextTreeItem *root; + QMap nameToItem[6]; + ContextTreeItem *nets_root; + ContextTreeItem *cells_root; +}; + +NEXTPNR_NAMESPACE_END + +#endif // TREEMODEL_H -- cgit v1.2.3 From 0d3d149c4f3bb3197bc2f4488ba8f856f0f35889 Mon Sep 17 00:00:00 2001 From: Miodrag Milanovic Date: Sat, 28 Jul 2018 16:56:16 +0200 Subject: Clean highlight selection if removed from tree --- gui/designwidget.cc | 12 ++++++++++++ gui/treemodel.cc | 8 -------- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/gui/designwidget.cc b/gui/designwidget.cc index b321aef1..ee6a14b4 100644 --- a/gui/designwidget.cc +++ b/gui/designwidget.cc @@ -223,6 +223,18 @@ 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()) == ctx->nets.end()) { + highlightSelected.erase(prev); + } + if (prev.key()->type() == ElementType::CELL && ctx->cells.find(prev.key()->id()) == ctx->cells.end()) { + highlightSelected.erase(prev); + } + } + treeModel->updateData(ctx); } QtProperty *DesignWidget::addTopLevelProperty(const QString &id) diff --git a/gui/treemodel.cc b/gui/treemodel.cc index 65a17a2f..5a064f57 100644 --- a/gui/treemodel.cc +++ b/gui/treemodel.cc @@ -166,10 +166,6 @@ void ContextTreeModel::updateData(Context *ctx) QMap::iterator prev = i; ++i; if (ctx->nets.find(ctx->id(prev.key().toStdString())) == ctx->nets.end()) { - /* if (treeWidget->currentItem() == prev.value()) - treeWidget->setCurrentItem(nets_root); - if (highlightSelected.contains(prev.value())) - highlightSelected.remove(prev.value());*/ delete prev.value(); nameToItem[3].erase(prev); } @@ -191,10 +187,6 @@ void ContextTreeModel::updateData(Context *ctx) QMap::iterator prev = i; ++i; if (ctx->cells.find(ctx->id(prev.key().toStdString())) == ctx->cells.end()) { - /* if (treeWidget->currentItem() == prev.value()) - treeWidget->setCurrentItem(cells_root); - if (highlightSelected.contains(prev.value())) - highlightSelected.remove(prev.value());*/ delete prev.value(); nameToItem[4].erase(prev); } -- cgit v1.2.3 From 9a30b6330b1997d07a8f18b87e2b413faf95094a Mon Sep 17 00:00:00 2001 From: Miodrag Milanovic Date: Sat, 28 Jul 2018 17:13:13 +0200 Subject: fix select multiple, and reinit model --- gui/designwidget.cc | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/gui/designwidget.cc b/gui/designwidget.cc index ee6a14b4..106c3146 100644 --- a/gui/designwidget.cc +++ b/gui/designwidget.cc @@ -30,7 +30,7 @@ NEXTPNR_NAMESPACE_BEGIN -DesignWidget::DesignWidget(QWidget *parent) : QWidget(parent), ctx(nullptr) +DesignWidget::DesignWidget(QWidget *parent) : QWidget(parent), ctx(nullptr), selectionModel(nullptr) { // Add tree view treeView = new QTreeView(); @@ -158,9 +158,6 @@ DesignWidget::DesignWidget(QWidget *parent) : QWidget(parent), ctx(nullptr) &DesignWidget::prepareMenuProperty); connect(propertyEditor->treeWidget(), &QTreeWidget::itemDoubleClicked, this, &DesignWidget::onItemDoubleClicked); - selectionModel = treeView->selectionModel(); - connect(selectionModel, SIGNAL(selectionChanged(const QItemSelection &, const QItemSelection &)), - SLOT(onSelectionChanged(const QItemSelection &, const QItemSelection &))); connect(treeView, &QTreeWidget::customContextMenuRequested, this, &DesignWidget::prepareMenuTree); history_index = -1; @@ -202,6 +199,8 @@ void DesignWidget::addToHistory(QModelIndex item) void DesignWidget::newContext(Context *ctx) { + if (!ctx) + return; highlightSelected.clear(); history_ignore = false; @@ -211,16 +210,13 @@ void DesignWidget::newContext(Context *ctx) highlightSelected.clear(); this->ctx = ctx; + treeView->setModel(nullptr); treeModel->loadData(ctx); - updateTree(); } void DesignWidget::updateTree() { - if (!ctx) - return; - clearProperties(); QMap::iterator i = highlightSelected.begin(); @@ -236,6 +232,10 @@ void DesignWidget::updateTree() } treeModel->updateData(ctx); + treeView->setModel(treeModel); + selectionModel = treeView->selectionModel(); + connect(selectionModel, SIGNAL(selectionChanged(const QItemSelection &, const QItemSelection &)), + SLOT(onSelectionChanged(const QItemSelection &, const QItemSelection &))); } QtProperty *DesignWidget::addTopLevelProperty(const QString &id) { @@ -310,21 +310,21 @@ QtProperty *DesignWidget::addSubGroup(QtProperty *topItem, const QString &name) void DesignWidget::onClickedBel(BelId bel, bool keep) { ContextTreeItem *item = treeModel->nodeForIdType(ElementType::BEL, ctx->getBelName(bel).c_str(ctx)); - selectionModel->setCurrentIndex(treeModel->indexFromNode(item), QItemSelectionModel::ClearAndSelect); + selectionModel->setCurrentIndex(treeModel->indexFromNode(item), keep ? QItemSelectionModel::Select : QItemSelectionModel::ClearAndSelect); Q_EMIT selected(getDecals(ElementType::BEL, ctx->getBelName(bel)), keep); } void DesignWidget::onClickedWire(WireId wire, bool keep) { ContextTreeItem *item = treeModel->nodeForIdType(ElementType::WIRE, ctx->getWireName(wire).c_str(ctx)); - selectionModel->setCurrentIndex(treeModel->indexFromNode(item), QItemSelectionModel::ClearAndSelect); + selectionModel->setCurrentIndex(treeModel->indexFromNode(item), keep ? QItemSelectionModel::Select : QItemSelectionModel::ClearAndSelect); Q_EMIT selected(getDecals(ElementType::WIRE, ctx->getWireName(wire)), keep); } void DesignWidget::onClickedPip(PipId pip, bool keep) { ContextTreeItem *item = treeModel->nodeForIdType(ElementType::PIP, ctx->getPipName(pip).c_str(ctx)); - selectionModel->setCurrentIndex(treeModel->indexFromNode(item), QItemSelectionModel::ClearAndSelect); + selectionModel->setCurrentIndex(treeModel->indexFromNode(item), keep ? QItemSelectionModel::Select : QItemSelectionModel::ClearAndSelect); Q_EMIT selected(getDecals(ElementType::PIP, ctx->getPipName(pip)), keep); } -- cgit v1.2.3 From ba2531edc04a9e57b69fe6a289444db2f69c44d2 Mon Sep 17 00:00:00 2001 From: Miodrag Milanovic Date: Sat, 28 Jul 2018 18:48:32 +0200 Subject: add proper info on model changes --- gui/designwidget.cc | 18 ++++++++++-------- gui/treemodel.cc | 17 +++++++++++++++++ 2 files changed, 27 insertions(+), 8 deletions(-) diff --git a/gui/designwidget.cc b/gui/designwidget.cc index 106c3146..0f01b3c5 100644 --- a/gui/designwidget.cc +++ b/gui/designwidget.cc @@ -160,6 +160,10 @@ DesignWidget::DesignWidget(QWidget *parent) : QWidget(parent), ctx(nullptr), sel connect(treeView, &QTreeWidget::customContextMenuRequested, this, &DesignWidget::prepareMenuTree); + selectionModel = treeView->selectionModel(); + connect(selectionModel, SIGNAL(selectionChanged(const QItemSelection &, const QItemSelection &)), + SLOT(onSelectionChanged(const QItemSelection &, const QItemSelection &))); + history_index = -1; history_ignore = false; @@ -210,7 +214,6 @@ void DesignWidget::newContext(Context *ctx) highlightSelected.clear(); this->ctx = ctx; - treeView->setModel(nullptr); treeModel->loadData(ctx); updateTree(); } @@ -232,10 +235,6 @@ void DesignWidget::updateTree() } treeModel->updateData(ctx); - treeView->setModel(treeModel); - selectionModel = treeView->selectionModel(); - connect(selectionModel, SIGNAL(selectionChanged(const QItemSelection &, const QItemSelection &)), - SLOT(onSelectionChanged(const QItemSelection &, const QItemSelection &))); } QtProperty *DesignWidget::addTopLevelProperty(const QString &id) { @@ -310,21 +309,24 @@ QtProperty *DesignWidget::addSubGroup(QtProperty *topItem, const QString &name) void DesignWidget::onClickedBel(BelId bel, bool keep) { ContextTreeItem *item = treeModel->nodeForIdType(ElementType::BEL, ctx->getBelName(bel).c_str(ctx)); - selectionModel->setCurrentIndex(treeModel->indexFromNode(item), keep ? QItemSelectionModel::Select : QItemSelectionModel::ClearAndSelect); + selectionModel->setCurrentIndex(treeModel->indexFromNode(item), + keep ? QItemSelectionModel::Select : QItemSelectionModel::ClearAndSelect); Q_EMIT selected(getDecals(ElementType::BEL, ctx->getBelName(bel)), keep); } void DesignWidget::onClickedWire(WireId wire, bool keep) { ContextTreeItem *item = treeModel->nodeForIdType(ElementType::WIRE, ctx->getWireName(wire).c_str(ctx)); - selectionModel->setCurrentIndex(treeModel->indexFromNode(item), keep ? QItemSelectionModel::Select : QItemSelectionModel::ClearAndSelect); + selectionModel->setCurrentIndex(treeModel->indexFromNode(item), + keep ? QItemSelectionModel::Select : QItemSelectionModel::ClearAndSelect); Q_EMIT selected(getDecals(ElementType::WIRE, ctx->getWireName(wire)), keep); } void DesignWidget::onClickedPip(PipId pip, bool keep) { ContextTreeItem *item = treeModel->nodeForIdType(ElementType::PIP, ctx->getPipName(pip).c_str(ctx)); - selectionModel->setCurrentIndex(treeModel->indexFromNode(item), keep ? QItemSelectionModel::Select : QItemSelectionModel::ClearAndSelect); + selectionModel->setCurrentIndex(treeModel->indexFromNode(item), + keep ? QItemSelectionModel::Select : QItemSelectionModel::ClearAndSelect); Q_EMIT selected(getDecals(ElementType::PIP, ctx->getPipName(pip)), keep); } diff --git a/gui/treemodel.cc b/gui/treemodel.cc index 5a064f57..86b272bb 100644 --- a/gui/treemodel.cc +++ b/gui/treemodel.cc @@ -53,6 +53,9 @@ void ContextTreeModel::loadData(Context *ctx) { if (!ctx) return; + + beginResetModel(); + delete root; root = new ContextTreeItem(); @@ -153,6 +156,8 @@ void ContextTreeModel::loadData(Context *ctx) cells_root = new ContextTreeItem("Cells"); root->addChild(cells_root); + + endResetModel(); } void ContextTreeModel::updateData(Context *ctx) @@ -160,14 +165,18 @@ void ContextTreeModel::updateData(Context *ctx) if (!ctx) return; + QModelIndex nets_index = indexFromNode(nets_root); // Remove nets not existing any more QMap::iterator i = nameToItem[3].begin(); while (i != nameToItem[3].end()) { QMap::iterator prev = i; ++i; if (ctx->nets.find(ctx->id(prev.key().toStdString())) == ctx->nets.end()) { + int pos = prev.value()->parent()->indexOf(prev.value()); + beginRemoveRows(nets_index, pos, pos); delete prev.value(); nameToItem[3].erase(prev); + endRemoveRows(); } } // Add nets to tree @@ -175,20 +184,26 @@ void ContextTreeModel::updateData(Context *ctx) auto id = item.first; QString name = QString(id.c_str(ctx)); if (!nameToItem[3].contains(name)) { + beginInsertRows(nets_index, nets_root->count() + 1, nets_root->count() + 1); ContextTreeItem *newItem = new ContextTreeItem(id, ElementType::NET, name); nets_root->addChild(newItem); nameToItem[3].insert(name, newItem); + endInsertRows(); } } + QModelIndex cell_index = indexFromNode(cells_root); // Remove cells not existing any more i = nameToItem[4].begin(); while (i != nameToItem[4].end()) { QMap::iterator prev = i; ++i; if (ctx->cells.find(ctx->id(prev.key().toStdString())) == ctx->cells.end()) { + int pos = prev.value()->parent()->indexOf(prev.value()); + beginRemoveRows(cell_index, pos, pos); delete prev.value(); nameToItem[4].erase(prev); + endRemoveRows(); } } // Add cells to tree @@ -196,9 +211,11 @@ void ContextTreeModel::updateData(Context *ctx) auto id = item.first; QString name = QString(id.c_str(ctx)); if (!nameToItem[4].contains(name)) { + beginInsertRows(cell_index, cells_root->count() + 1, cells_root->count() + 1); ContextTreeItem *newItem = new ContextTreeItem(id, ElementType::CELL, name); cells_root->addChild(newItem); nameToItem[4].insert(name, newItem); + endInsertRows(); } } } -- cgit v1.2.3 From 7c8865d2fc9c2d6b19185627d650448df858fe75 Mon Sep 17 00:00:00 2001 From: Miodrag Milanovic Date: Sun, 29 Jul 2018 10:56:36 +0200 Subject: Added sorting --- gui/treemodel.cc | 51 +++++++++++++++++++++++++++++++++++++-------------- gui/treemodel.h | 1 + 2 files changed, 38 insertions(+), 14 deletions(-) diff --git a/gui/treemodel.cc b/gui/treemodel.cc index 86b272bb..59391f02 100644 --- a/gui/treemodel.cc +++ b/gui/treemodel.cc @@ -21,6 +21,11 @@ NEXTPNR_NAMESPACE_BEGIN +static bool contextTreeItemLessThan(const ContextTreeItem *v1, const ContextTreeItem *v2) + { + return v1->name() < v2->name(); + } + ContextTreeItem::ContextTreeItem() { parentNode = nullptr; } ContextTreeItem::ContextTreeItem(QString name) @@ -45,6 +50,13 @@ void ContextTreeItem::addChild(ContextTreeItem *item) children.append(item); } +void ContextTreeItem::sort() +{ + for (auto item : children) + if (item->count()>1) item->sort(); + qSort(children.begin(), children.end(), contextTreeItemLessThan); +} + ContextTreeModel::ContextTreeModel(QObject *parent) : QAbstractItemModel(parent) { root = new ContextTreeItem(); } ContextTreeModel::~ContextTreeModel() { delete root; } @@ -91,8 +103,9 @@ void ContextTreeModel::loadData(Context *ctx) } parent = bel_items[name]; } - } - + } + bels_root->sort(); + ContextTreeItem *wire_root = new ContextTreeItem("Wires"); root->addChild(wire_root); QMap wire_items; @@ -121,6 +134,7 @@ void ContextTreeModel::loadData(Context *ctx) parent = wire_items[name]; } } + wire_root->sort(); ContextTreeItem *pip_root = new ContextTreeItem("Pips"); root->addChild(pip_root); @@ -150,6 +164,7 @@ void ContextTreeModel::loadData(Context *ctx) parent = pip_items[name]; } } + pip_root->sort(); nets_root = new ContextTreeItem("Nets"); root->addChild(nets_root); @@ -165,18 +180,20 @@ void ContextTreeModel::updateData(Context *ctx) if (!ctx) return; - QModelIndex nets_index = indexFromNode(nets_root); + beginResetModel(); + + //QModelIndex nets_index = indexFromNode(nets_root); // Remove nets not existing any more QMap::iterator i = nameToItem[3].begin(); while (i != nameToItem[3].end()) { QMap::iterator prev = i; ++i; if (ctx->nets.find(ctx->id(prev.key().toStdString())) == ctx->nets.end()) { - int pos = prev.value()->parent()->indexOf(prev.value()); - beginRemoveRows(nets_index, pos, pos); + //int pos = prev.value()->parent()->indexOf(prev.value()); + //beginRemoveRows(nets_index, pos, pos); delete prev.value(); nameToItem[3].erase(prev); - endRemoveRows(); + //endRemoveRows(); } } // Add nets to tree @@ -184,26 +201,28 @@ void ContextTreeModel::updateData(Context *ctx) auto id = item.first; QString name = QString(id.c_str(ctx)); if (!nameToItem[3].contains(name)) { - beginInsertRows(nets_index, nets_root->count() + 1, nets_root->count() + 1); + //beginInsertRows(nets_index, nets_root->count() + 1, nets_root->count() + 1); ContextTreeItem *newItem = new ContextTreeItem(id, ElementType::NET, name); nets_root->addChild(newItem); nameToItem[3].insert(name, newItem); - endInsertRows(); + //endInsertRows(); } } - QModelIndex cell_index = indexFromNode(cells_root); + nets_root->sort(); + + //QModelIndex cell_index = indexFromNode(cells_root); // Remove cells not existing any more i = nameToItem[4].begin(); while (i != nameToItem[4].end()) { QMap::iterator prev = i; ++i; if (ctx->cells.find(ctx->id(prev.key().toStdString())) == ctx->cells.end()) { - int pos = prev.value()->parent()->indexOf(prev.value()); - beginRemoveRows(cell_index, pos, pos); + //int pos = prev.value()->parent()->indexOf(prev.value()); + //beginRemoveRows(cell_index, pos, pos); delete prev.value(); nameToItem[4].erase(prev); - endRemoveRows(); + //endRemoveRows(); } } // Add cells to tree @@ -211,13 +230,17 @@ void ContextTreeModel::updateData(Context *ctx) auto id = item.first; QString name = QString(id.c_str(ctx)); if (!nameToItem[4].contains(name)) { - beginInsertRows(cell_index, cells_root->count() + 1, cells_root->count() + 1); + //beginInsertRows(cell_index, cells_root->count() + 1, cells_root->count() + 1); ContextTreeItem *newItem = new ContextTreeItem(id, ElementType::CELL, name); cells_root->addChild(newItem); nameToItem[4].insert(name, newItem); - endInsertRows(); + //endInsertRows(); } } + + cells_root->sort(); + + endResetModel(); } int ContextTreeModel::rowCount(const QModelIndex &parent) const { return nodeFromIndex(parent)->count(); } diff --git a/gui/treemodel.h b/gui/treemodel.h index a976c5bc..a85c290a 100644 --- a/gui/treemodel.h +++ b/gui/treemodel.h @@ -52,6 +52,7 @@ class ContextTreeItem IdString id() const { return itemId; } ElementType type() const { return itemType; } QString name() const { return itemName; } + void sort(); private: ContextTreeItem *parentNode; QList children; -- cgit v1.2.3 From 3b354c2a51f9d13d1f7d4d1f8acad200f9733bee Mon Sep 17 00:00:00 2001 From: Clifford Wolf Date: Sun, 29 Jul 2018 12:30:11 +0200 Subject: fix randtag bug in router1 Signed-off-by: Clifford Wolf --- common/router1.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/router1.cc b/common/router1.cc index 8a05236f..4ef7df64 100644 --- a/common/router1.cc +++ b/common/router1.cc @@ -216,7 +216,7 @@ struct Router next_qw.pip = pip; next_qw.delay = next_delay; next_qw.togo = ctx->estimateDelay(next_wire, dst_wire); - qw.randtag = ctx->rng(); + next_qw.randtag = ctx->rng(); visited[next_qw.wire] = next_qw; queue.push(next_qw); -- cgit v1.2.3 From 1566e9afc37f384eed43f9b14a90447b8d8ee061 Mon Sep 17 00:00:00 2001 From: Miodrag Milanovic Date: Sun, 29 Jul 2018 13:42:00 +0200 Subject: python interpreter is mandatory in any case --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index cc712e72..41bd4aab 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -58,9 +58,9 @@ if (BUILD_GUI AND NOT BUILD_PYTHON) message(FATAL_ERROR "GUI requires Python to build") endif() +find_package(PythonInterp 3.5 REQUIRED) if (BUILD_PYTHON) # TODO: sensible minimum Python version - find_package(PythonInterp 3.5 REQUIRED) find_package(PythonLibs 3.5 REQUIRED) else() add_definitions("-DNO_PYTHON") -- cgit v1.2.3 From f448d7ca77ff5481f9ec4d9278ad653504aec01c Mon Sep 17 00:00:00 2001 From: Clifford Wolf Date: Sun, 29 Jul 2018 11:43:04 +0000 Subject: Update README.md --- README.md | 214 ++++++++++++++++++++++++++++++++++++++++---------------------- 1 file changed, 137 insertions(+), 77 deletions(-) diff --git a/README.md b/README.md index da38500d..8154f69f 100644 --- a/README.md +++ b/README.md @@ -1,87 +1,147 @@ nextpnr -- a portable FPGA place and route tool =============================================== -Supported Architectures ------------------------ +nextpnr is an FPGA place and route tool with emphasis on supporting a wide +range of real-world FPGA devices. It currently supports Lattice iCE40 devices +and Lattice ECP5 devices, as well as a "generic" back-end for user-defined +architectures. (ECP5 and "generic" support are still experimental.) -- iCE40 -- ECP5 +Prerequisites +------------- -Prequisites ------------ - - - CMake 3.3 or later - - Modern C++11 compiler (`clang-format` required for development) - - Qt5 or later (`qt5-default` for Ubuntu 16.04) - - Python 3.5 or later, including development libraries (`python3-dev` for Ubuntu) - - on Windows make sure to install same version as supported by [vcpkg](https://github.com/Microsoft/vcpkg/blob/master/ports/python3/CONTROL) - - Boost libraries (`libboost-dev` or `libboost-all-dev` for Ubuntu) - - Icestorm, with chipdbs installed in `/usr/local/share/icebox` - - Latest git Yosys is required to synthesise the demo design - - For building on Windows with MSVC, usage of vcpkg is advised for dependency installation. - - For 32 bit builds: `vcpkg install boost-filesystem boost-program-options boost-thread boost-python qt5-base` - - For 64 bit builds: `vcpkg install boost-filesystem:x64-windows boost-program-options:x64-windows boost-thread:x64-windows boost-python:x64-windows qt5-base:x64-windows` - - For building on macOS, brew utility is needed. - - Install all needed packages `brew install cmake python boost boost-python3 qt5` - - Do not forget to add qt5 in path as well `echo 'export PATH="/usr/local/opt/qt/bin:$PATH"' >> ~/.bash_profile` - - For ECP5 support, you must download [Project Trellis](https://github.com/SymbiFlow/prjtrellis), then follow its instructions to - download the latest database and build _libtrellis_. - +The following packages need to be installed for building nextpnr, independent +of the selected architecture: + +- CMake 3.3 or later +- Modern C++11 compiler (`clang-format` required for development) +- Qt5 or later (`qt5-default` for Ubuntu 16.04) +- Python 3.5 or later, including development libraries (`python3-dev` for Ubuntu) + - on Windows make sure to install same version as supported by [vcpkg](https://github.com/Microsoft/vcpkg/blob/master/ports/python3/CONTROL) +- Boost libraries (`libboost-dev` or `libboost-all-dev` for Ubuntu) +- Latest git Yosys is required to synthesise the demo design +- For building on Windows with MSVC, usage of vcpkg is advised for dependency installation. + - For 32 bit builds: `vcpkg install boost-filesystem boost-program-options boost-thread boost-python qt5-base` + - For 64 bit builds: `vcpkg install boost-filesystem:x64-windows boost-program-options:x64-windows boost-thread:x64-windows boost-python:x64-windows qt5-base:x64-windows` +- For building on macOS, brew utility is needed. + - Install all needed packages `brew install cmake python boost boost-python3 qt5` + - Do not forget to add qt5 in path as well `echo 'export PATH="/usr/local/opt/qt/bin:$PATH"' >> ~/.bash_profile` + +Getting started +--------------- + +### nextpnr-ice40 + +To build the iCE40 version of nextpnr, install [icestorm](http://www.clifford.at/icestorm/) with chipdbs installed in `/usr/local/share/icebox`. +Then build and install `nextpnr-ice40` using the following commands: + +``` +cmake -DARCH=ice40 . +make -j$(nproc) +sudo make install +``` + +A simple example that runs on the iCEstick dev board can be found in `ice40/blinky.*`. +Usage example: + +``` +cd ice40 +yosys -p 'synth_ice40 -top blinky -json blinky.json' blinky.v # synthesize into blinky.json +nextpnr-ice40 --hx1k --json blinky.json --pcf blinky.pcf --asc blinky.asc # run place and route +icepack blinky.asc blinky.bin # generate binary bitstream file +iceprog blinky.bin # upload design to iCEstick +``` + +Running nextpnr in GUI mode: + +``` +nextpnr-ice40 --json blinky.json --pcf blinky.pcf --asc blinky.asc --gui +``` + +(Use the toolbar buttons or the Python command console to perform actions +such as pack, place, route, and write output files.) + +### nextpnr-ecp5 + +For ECP5 support, you must download [Project Trellis](https://github.com/SymbiFlow/prjtrellis), +then follow its instructions to download the latest database and build _libtrellis_. + +``` +cmake -DARCH=ecp5 . +make -j$(nproc) +sudo make install +``` + +- For an ECP5 blinky, first synthesise using `yosys blinky.ys` in `ecp5/synth`. + - Then run ECP5 place-and route using `./nextpnr-ecp5 --json ecp5/synth/blinky.json --basecfg ecp5/synth/ulx3s_empty.config --bit ecp5/synth/ulx3s.bit` + - Note that `ulx3s_empty.config` contains fixed/unknown bits to be copied to the output bitstream + - You can also use `--textcfg out.config` to write a text file describing the bitstream for debugging + +### nextpnr-generic + +The generic target allows to run place and route for an arbitrary custom architecture. + +``` +cmake -DARCH=generic . +make -j$(nproc) +sudo make install +``` + +TBD: Getting started example for generic target. + +Additional notes for building nextpnr +------------------------------------- + +Use cmake `-D` options to specify which version of nextpnr you want to build. + +Use `-DARCH=...` to set the architecture. It is semicolon separated list. +Use `cmake . -DARCH=all` to build all supported architectures. + +The following runs a debug build of the iCE40 architecture without GUI +and without Python support and only HX1K support: + +``` +cmake -DARCH=ice40 -DCMAKE_BUILD_TYPE=Debug -DBUILD_PYTHON=OFF -DBUILD_GUI=OFF -DICE40_HX1K_ONLY=1 . +make -j$(nproc) +``` + +Notes for developers +-------------------- -Building --------- - - - Specifying target architecture is mandatory use ARCH parameter to set it. It is semicolon separated list. - - Use `cmake . -DARCH=all` to build all supported targets - - For example `cmake . -DARCH=ice40` would build just ICE40 support - - Use CMake to generate the Makefiles (only needs to be done when `CMakeLists.txt` changes) - - For an iCE40 debug build, run `cmake -DARCH=ice40 -DCMAKE_BUILD_TYPE=Debug .` - - For an iCE40 debug build with HX1K support only, run `cmake -DARCH=ice40 -DCMAKE_BUILD_TYPE=Debug -DICE40_HX1K_ONLY=1 .` - - For an iCE40 and ECP5 release build, run `cmake -DARCH="ice40;ecp5" .` - - Add `-DCMAKE_INSTALL_PREFIX=/your/install/prefix` to use a different install prefix to the default `/usr/local` - - For MSVC build with vcpkg use `-DCMAKE_TOOLCHAIN_FILE=C:/vcpkg/scripts/buildsystems/vcpkg.cmake` using your vcpkg location - - For MSVC x64 build adding `-G"Visual Studio 14 2015 Win64"` is needed. - - For ECP5 support, you must also specify the path to Project Trellis using `-DTRELLIS_ROOT=/path/trellis` - - Use Make to run the build itself - - For all binary targets, just run `make` - - For just the iCE40 CLI&GUI binary, run `make nextpnr-ice40` - - To build binary without Python support, use `-DBUILD_PYTHON=OFF` - - To build binary without GUI, use `-DBUILD_GUI=OFF` - - For minimal binary without Python and GUI, use `-DBUILD_PYTHON=OFF -DBUILD_GUI=OFF` - - For just the iCE40 Python module, run `make nextpnrpy_ice40` - - Using too many parallel jobs may lead to out-of-memory issues due to the significant memory needed to build the chipdbs - - To install nextpnr, run `make install` +- All code is formatted using `clang-format` according to the style rules in `.clang-format` (LLVM based with + increased indent widths and brace wraps after classes). +- To automatically format all source code, run `make clangformat`. +- See the wiki for additional documentation on the architecture API. Testing ------- - - To build test binaries as well, use `-DBUILD_TESTS=OFF` and after run `make tests` to run them, or you can run separate binaries. - - To use code sanitizers use the `cmake` options: - - `-DSANITIZE_ADDRESS=ON` - - `-DSANITIZE_MEMORY=ON -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++` - - `-DSANITIZE_THREAD=ON` - - `-DSANITIZE_UNDEFINED=ON` - - Running valgrind example `valgrind --leak-check=yes --tool=memcheck ./nextpnr-ice40 --json ice40/blinky.json` - -Running --------- - - - To run the CLI binary, just run `./nextpnr-ice40` (you should see command line help) - - To start the UI, run `./nextpnr-ice40 --gui` - - The Python module is called `nextpnrpy_ice40.so`. To test it, run `PYTHONPATH=. python3 python/python_mod_test.py` - - Run `yosys blinky.ys` in `ice40/` to synthesise the blinky design and - produce `blinky.json`. - - To place-and-route the blinky using nextpnr, run `./nextpnr-ice40 --hx1k --json ice40/blinky.json --pcf ice40/blinky.pcf --asc blinky.asc` - - - For an ECP5 blinky, first synthesise using `yosys blinky.ys` in `ecp5/synth`. - - Then run ECP5 place-and route using - `./nextpnr-ecp5 --json ecp5/synth/blinky.json --basecfg ecp5/synth/ulx3s_empty.config --bit ecp5/synth/ulx3s.bit` - - Note that `ulx3s_empty.config` contains fixed/unknown bits to be copied to the output bitstream - - You can also use `--textcfg out.config` to write a text file describing the bitstream for debugging - -Notes -------- - - - All code is formatted using `clang-format` according to the style rules in `.clang-format` (LLVM based with - increased indent widths and brace wraps after classes). - - To automatically format all source code, run `make clangformat`. +- To build test binaries as well, use `-DBUILD_TESTS=ON` and after `make` run `make tests` to run them, or you can run separate binaries. +- To use code sanitizers use the `cmake` options: + - `-DSANITIZE_ADDRESS=ON` + - `-DSANITIZE_MEMORY=ON -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++` + - `-DSANITIZE_THREAD=ON` + - `-DSANITIZE_UNDEFINED=ON` +- Running valgrind example `valgrind --leak-check=yes --tool=memcheck ./nextpnr-ice40 --json ice40/blinky.json` + +Links and references +-------------------- + +### Synthesis, simulation, and logic optimization + +- [Yosys](http://www.clifford.at/yosys/) +- [Icarus Verilog](http://iverilog.icarus.com/) +- [ABC](https://people.eecs.berkeley.edu/~alanmi/abc/) + +### FPGA bitstream documentation (and tools) projects + +- [Project IceStorm (Lattice iCE40)](http://www.clifford.at/icestorm/) +- [Project Trellis (Lattice ECP5)](https://symbiflow.github.io/prjtrellis-db/) +- [Project X-Ray (Xilinx 7-Series)](https://symbiflow.github.io/prjxray-db/) +- [Project Chibi (Intel MAX-V)](https://github.com/rqou/project-chibi) + +### Other FOSS place and route tools (FPGA and ASIC) + +- [Arachne PNR](https://github.com/cseed/arachne-pnr) +- [VPR/VTR](https://verilogtorouting.org/) +- [graywolf/timberwolf](https://github.com/rubund/graywolf) +- [qrouter](http://opencircuitdesign.com/qrouter/) -- cgit v1.2.3 From f466ad0faf6d5757d1cb3259486bb7604b9064f2 Mon Sep 17 00:00:00 2001 From: David Shah Date: Sun, 29 Jul 2018 14:17:02 +0200 Subject: Update README.md wrt ECP5 Signed-off-by: David Shah --- README.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 8154f69f..dfe2af37 100644 --- a/README.md +++ b/README.md @@ -71,11 +71,16 @@ make -j$(nproc) sudo make install ``` -- For an ECP5 blinky, first synthesise using `yosys blinky.ys` in `ecp5/synth`. + - For an ECP5 blinky, first synthesise using `yosys blinky.ys` in `ecp5/synth`. - Then run ECP5 place-and route using `./nextpnr-ecp5 --json ecp5/synth/blinky.json --basecfg ecp5/synth/ulx3s_empty.config --bit ecp5/synth/ulx3s.bit` - Note that `ulx3s_empty.config` contains fixed/unknown bits to be copied to the output bitstream - You can also use `--textcfg out.config` to write a text file describing the bitstream for debugging + - More examples of the ECP5 flow for a range of boards can be found in the [Project Trellis Examples](https://github.com/SymbiFlow/prjtrellis/tree/master/examples). + + - Currently the ECP5 flow supports LUTs, flipflops and IO. IO must be instantiated using `TRELLIS_IO` primitives and constraints specified + using `LOC` and `IO_TYPE` attributes on those instances, as is used in the examples. + ### nextpnr-generic The generic target allows to run place and route for an arbitrary custom architecture. -- cgit v1.2.3 From 91227b775326784f7b36daa560445074ff5669c7 Mon Sep 17 00:00:00 2001 From: Miodrag Milanovic Date: Sun, 29 Jul 2018 15:21:34 +0200 Subject: double click on tree, zoom on selected object --- gui/basewindow.cc | 1 + gui/designwidget.cc | 5 +++-- gui/designwidget.h | 2 ++ 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/gui/basewindow.cc b/gui/basewindow.cc index 6e997011..6d5e97f5 100644 --- a/gui/basewindow.cc +++ b/gui/basewindow.cc @@ -86,6 +86,7 @@ BaseMainWindow::BaseMainWindow(std::unique_ptr context, QWidget *parent connect(fpgaView, SIGNAL(clickedBel(BelId, bool)), designview, SLOT(onClickedBel(BelId, bool))); connect(fpgaView, SIGNAL(clickedWire(WireId, bool)), designview, SLOT(onClickedWire(WireId, bool))); connect(fpgaView, SIGNAL(clickedPip(PipId, bool)), designview, SLOT(onClickedPip(PipId, bool))); + connect(designview, SIGNAL(zoomSelected()), fpgaView, SLOT(zoomSelected())); connect(designview, SIGNAL(highlight(std::vector, int)), fpgaView, SLOT(onHighlightGroupChanged(std::vector, int))); diff --git a/gui/designwidget.cc b/gui/designwidget.cc index 0f01b3c5..5107fbee 100644 --- a/gui/designwidget.cc +++ b/gui/designwidget.cc @@ -158,8 +158,8 @@ DesignWidget::DesignWidget(QWidget *parent) : QWidget(parent), ctx(nullptr), sel &DesignWidget::prepareMenuProperty); connect(propertyEditor->treeWidget(), &QTreeWidget::itemDoubleClicked, this, &DesignWidget::onItemDoubleClicked); - connect(treeView, &QTreeWidget::customContextMenuRequested, this, &DesignWidget::prepareMenuTree); - + connect(treeView, &QTreeView::customContextMenuRequested, this, &DesignWidget::prepareMenuTree); + connect(treeView, &QTreeView::doubleClicked, this, &DesignWidget::onDoubleClicked); selectionModel = treeView->selectionModel(); connect(selectionModel, SIGNAL(selectionChanged(const QItemSelection &, const QItemSelection &)), SLOT(onSelectionChanged(const QItemSelection &, const QItemSelection &))); @@ -713,4 +713,5 @@ void DesignWidget::onItemDoubleClicked(QTreeWidgetItem *item, int column) selectionModel->setCurrentIndex(treeModel->indexFromNode(it), QItemSelectionModel::ClearAndSelect); } +void DesignWidget::onDoubleClicked(const QModelIndex &index) { Q_EMIT zoomSelected(); } NEXTPNR_NAMESPACE_END diff --git a/gui/designwidget.h b/gui/designwidget.h index bbd188cd..27ead589 100644 --- a/gui/designwidget.h +++ b/gui/designwidget.h @@ -56,12 +56,14 @@ class DesignWidget : public QWidget void info(std::string text); void selected(std::vector decal, bool keep); void highlight(std::vector decal, int group); + void zoomSelected(); private Q_SLOTS: void prepareMenuProperty(const QPoint &pos); void prepareMenuTree(const QPoint &pos); void onSelectionChanged(const QItemSelection &selected, const QItemSelection &deselected); void onItemDoubleClicked(QTreeWidgetItem *item, int column); + void onDoubleClicked(const QModelIndex &index); public Q_SLOTS: void newContext(Context *ctx); void updateTree(); -- cgit v1.2.3