diff options
Diffstat (limited to 'gui/designwidget.cc')
| -rw-r--r-- | gui/designwidget.cc | 672 | 
1 files changed, 404 insertions, 268 deletions
diff --git a/gui/designwidget.cc b/gui/designwidget.cc index edf9f1ec..335ed929 100644 --- a/gui/designwidget.cc +++ b/gui/designwidget.cc @@ -29,16 +29,6 @@  NEXTPNR_NAMESPACE_BEGIN
 -enum class ElementType
 -{
 -    NONE,
 -    BEL,
 -    WIRE,
 -    PIP,
 -    NET,
 -    CELL
 -};
 -
  class ElementTreeItem : public QTreeWidgetItem
  {
    public:
 @@ -87,29 +77,53 @@ DesignWidget::DesignWidget(QWidget *parent) : QWidget(parent), ctx(nullptr), net      propertyEditor = new QtTreePropertyBrowser(this);
      propertyEditor->setFactoryForManager(variantManager, variantFactory);
      propertyEditor->setPropertiesWithoutValueMarked(true);
 -
      propertyEditor->show();
 -    
 +    propertyEditor->treeWidget()->setContextMenuPolicy(Qt::CustomContextMenu);
 +
      QLineEdit *lineEdit = new QLineEdit();
      lineEdit->setClearButtonEnabled(true);
      lineEdit->addAction(QIcon(":/icons/resources/zoom.png"), QLineEdit::LeadingPosition);
      lineEdit->setPlaceholderText("Search...");
 -    actionFirst = new QAction("", this);    
 +    actionFirst = new QAction("", this);
      actionFirst->setIcon(QIcon(":/icons/resources/resultset_first.png"));
      actionFirst->setEnabled(false);
 -
 -    actionPrev = new QAction("", this);    
 +    connect(actionFirst, &QAction::triggered, this, [this] {
 +        history_ignore = true;
 +        history_index = 0;
 +        treeWidget->setCurrentItem(history.at(history_index));
 +        updateButtons();
 +    });
 +
 +    actionPrev = new QAction("", this);
      actionPrev->setIcon(QIcon(":/icons/resources/resultset_previous.png"));
      actionPrev->setEnabled(false);
 -
 -    actionNext = new QAction("", this);    
 +    connect(actionPrev, &QAction::triggered, this, [this] {
 +        history_ignore = true;
 +        history_index--;
 +        treeWidget->setCurrentItem(history.at(history_index));
 +        updateButtons();
 +    });
 +
 +    actionNext = new QAction("", this);
      actionNext->setIcon(QIcon(":/icons/resources/resultset_next.png"));
      actionNext->setEnabled(false);
 -
 -    actionLast = new QAction("", this);    
 +    connect(actionNext, &QAction::triggered, this, [this] {
 +        history_ignore = true;
 +        history_index++;
 +        treeWidget->setCurrentItem(history.at(history_index));
 +        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);
 +        treeWidget->setCurrentItem(history.at(history_index));
 +        updateButtons();
 +    });
      QToolBar *toolbar = new QToolBar();
      toolbar->addAction(actionFirst);
 @@ -153,16 +167,61 @@ DesignWidget::DesignWidget(QWidget *parent) : QWidget(parent), ctx(nullptr), net      setLayout(mainLayout);
      // Connection
 -    connect(treeWidget, &QTreeWidget::customContextMenuRequested, this, &DesignWidget::prepareMenu);
 +    connect(propertyEditor->treeWidget(), &QTreeWidget::customContextMenuRequested, this,
 +            &DesignWidget::prepareMenuProperty);
 +    connect(propertyEditor->treeWidget(), &QTreeWidget::itemDoubleClicked, this, &DesignWidget::onItemDoubleClicked);
      connect(treeWidget, SIGNAL(itemSelectionChanged()), SLOT(onItemSelectionChanged()));
 +    connect(treeWidget, &QTreeWidget::customContextMenuRequested, this, &DesignWidget::prepareMenuTree);
 +
 +    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(QTreeWidgetItem *item)
 +{
 +    if (!history_ignore) {
 +        int count = int(history.size());
 +        for (int i = count - 1; i > history_index; i--)
 +            history.pop_back();
 +        history.push_back(item);
 +        history_index++;
 +    }
 +    history_ignore = false;
 +    updateButtons();
 +}
 +
  void DesignWidget::newContext(Context *ctx)
  {
      treeWidget->clear();
 +    history_ignore = false;
 +    history_index = -1;
 +    history.clear();
 +    updateButtons();
 +
 +    for (int i = 0; i < 6; i++)
 +        nameToItem[i].clear();
 +
      this->ctx = ctx;
      // Add bels to tree
 @@ -171,6 +230,7 @@ void DesignWidget::newContext(Context *ctx)      bel_root->setText(0, "Bels");
      treeWidget->insertTopLevelItem(0, bel_root);
      if (ctx) {
 +        Q_EMIT contextLoadStatus("Configuring bels...");
          for (auto bel : ctx->getBels()) {
              auto id = ctx->getBelName(bel);
              QStringList items = QString(id.c_str(ctx)).split("/");
 @@ -182,7 +242,7 @@ void DesignWidget::newContext(Context *ctx)                  name += items.at(i);
                  if (!bel_items.contains(name)) {
                      if (i == items.size() - 1)
 -                        bel_items.insert(name, new IdStringTreeItem(id, ElementType::BEL, items.at(i), parent));
 +                        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));
                  }
 @@ -193,6 +253,9 @@ void DesignWidget::newContext(Context *ctx)      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);
 @@ -200,6 +263,7 @@ void DesignWidget::newContext(Context *ctx)      wire_root->setText(0, "Wires");
      treeWidget->insertTopLevelItem(0, wire_root);
      if (ctx) {
 +        Q_EMIT contextLoadStatus("Configuring wires...");
          for (auto wire : ctx->getWires()) {
              auto id = ctx->getWireName(wire);
              QStringList items = QString(id.c_str(ctx)).split("/");
 @@ -211,7 +275,7 @@ void DesignWidget::newContext(Context *ctx)                  name += items.at(i);
                  if (!wire_items.contains(name)) {
                      if (i == items.size() - 1)
 -                        wire_items.insert(name, new IdStringTreeItem(id, ElementType::WIRE, items.at(i), parent));
 +                        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));
                  }
 @@ -222,13 +286,16 @@ void DesignWidget::newContext(Context *ctx)      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<QString, QTreeWidgetItem *> pip_items;
      pip_root->setText(0, "Pips");
      treeWidget->insertTopLevelItem(0, pip_root);
      if (ctx) {
 +        Q_EMIT contextLoadStatus("Configuring pips...");
          for (auto pip : ctx->getPips()) {
              auto id = ctx->getPipName(pip);
              QStringList items = QString(id.c_str(ctx)).split("/");
 @@ -240,7 +307,7 @@ void DesignWidget::newContext(Context *ctx)                  name += items.at(i);
                  if (!pip_items.contains(name)) {
                      if (i == items.size() - 1)
 -                        pip_items.insert(name, new IdStringTreeItem(id, ElementType::PIP, items.at(i), parent));
 +                        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));
                  }
 @@ -251,6 +318,9 @@ void DesignWidget::newContext(Context *ctx)      for (auto pip : pip_items.toStdMap()) {
          pip_root->addChild(pip.second);
      }
 +    for (auto pip : nameToItem[2].toStdMap()) {
 +        pip_root->addChild(pip.second);
 +    }
      // Add nets to tree
      nets_root = new QTreeWidgetItem(treeWidget);
 @@ -261,6 +331,8 @@ void DesignWidget::newContext(Context *ctx)      cells_root = new QTreeWidgetItem(treeWidget);
      cells_root->setText(0, "Cells");
      treeWidget->insertTopLevelItem(0, cells_root);
 +
 +    Q_EMIT finishContextLoad();
  }
  void DesignWidget::updateTree()
 @@ -268,45 +340,48 @@ void DesignWidget::updateTree()      clearProperties();
      delete nets_root;
      delete cells_root;
 +    nameToItem[3].clear();
 +    nameToItem[4].clear();
      // Add nets to tree
      nets_root = new QTreeWidgetItem(treeWidget);
 -    QMap<QString, QTreeWidgetItem *> nets_items;
      nets_root->setText(0, "Nets");
      treeWidget->insertTopLevelItem(0, nets_root);
      if (ctx) {
          for (auto &item : ctx->nets) {
              auto id = item.first;
              QString name = QString(id.c_str(ctx));
 -            nets_items.insert(name, new IdStringTreeItem(id, ElementType::NET, name, nullptr));
 +            IdStringTreeItem *newItem = new IdStringTreeItem(id, ElementType::NET, name, nullptr);
 +            nameToItem[3].insert(name, newItem);
          }
      }
 -    for (auto item : nets_items.toStdMap()) {
 +    for (auto item : nameToItem[3].toStdMap()) {
          nets_root->addChild(item.second);
      }
      // Add cells to tree
      cells_root = new QTreeWidgetItem(treeWidget);
 -    QMap<QString, QTreeWidgetItem *> cells_items;
      cells_root->setText(0, "Cells");
      treeWidget->insertTopLevelItem(0, cells_root);
      if (ctx) {
          for (auto &item : ctx->cells) {
              auto id = item.first;
              QString name = QString(id.c_str(ctx));
 -            cells_items.insert(name, new IdStringTreeItem(id, ElementType::CELL, name, nullptr));
 +            IdStringTreeItem *newItem = new IdStringTreeItem(id, ElementType::CELL, name, nullptr);
 +            nameToItem[4].insert(name, newItem);
          }
      }
 -    for (auto item : cells_items.toStdMap()) {
 +    for (auto item : nameToItem[4].toStdMap()) {
          cells_root->addChild(item.second);
      }
  }
 -
 -void DesignWidget::addProperty(QtProperty *property, const QString &id)
 +QtProperty *DesignWidget::addTopLevelProperty(const QString &id)
  {
 -    propertyToId[property] = id;
 -    idToProperty[id] = property;
 -    propertyEditor->addProperty(property);
 +    QtProperty *topItem = groupManager->addProperty(id);
 +    propertyToId[topItem] = id;
 +    idToProperty[id] = topItem;
 +    propertyEditor->addProperty(topItem);
 +    return topItem;
  }
  void DesignWidget::clearProperties()
 @@ -320,10 +395,73 @@ void DesignWidget::clearProperties()      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 "";
 +}
 +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")
 +        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;
 +}
 +
 +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));
 +    topItem->addSubProperty(item);
 +}
 +
 +QtProperty *DesignWidget::addSubGroup(QtProperty *topItem, const QString &name)
 +{
 +    QtProperty *item = groupManager->addProperty(name);
 +    topItem->addSubProperty(item);
 +    return item;
 +}
 +
  void DesignWidget::onItemSelectionChanged()
  {
 -    if (treeWidget->selectedItems().size()== 0) return;
 -    
 +    if (treeWidget->selectedItems().size() == 0)
 +        return;
 +
      QTreeWidgetItem *clickItem = treeWidget->selectedItems().at(0);
      if (!clickItem->parent())
 @@ -336,36 +474,24 @@ void DesignWidget::onItemSelectionChanged()      std::vector<DecalXY> decals;
 +    addToHistory(clickItem);
 +
      clearProperties();
      if (type == ElementType::BEL) {
          IdString c = static_cast<IdStringTreeItem *>(clickItem)->getData();
          BelId bel = ctx->getBelByName(c);
 -        
 -        decals.push_back(ctx->getBelDecal(bel));        
 -        Q_EMIT selected(decals);
 -
 -        QtProperty *topItem = groupManager->addProperty("Bel");
 -        addProperty(topItem, "Bel");
 -
 -        QtVariantProperty *nameItem = readOnlyManager->addProperty(QVariant::String, "Name");
 -        nameItem->setValue(c.c_str(ctx));
 -        topItem->addSubProperty(nameItem);
 -        QtVariantProperty *typeItem = readOnlyManager->addProperty(QVariant::String, "Type");
 -        typeItem->setValue(ctx->belTypeToId(ctx->getBelType(bel)).c_str(ctx));
 -        topItem->addSubProperty(typeItem);
 -
 -        QtVariantProperty *availItem = readOnlyManager->addProperty(QVariant::Bool, "Available");
 -        availItem->setValue(ctx->checkBelAvail(bel));
 -        topItem->addSubProperty(availItem);
 +        decals.push_back(ctx->getBelDecal(bel));
 +        Q_EMIT selected(decals);
 -        QtVariantProperty *cellItem = readOnlyManager->addProperty(QVariant::String, "Bound Cell");
 -        cellItem->setValue(ctx->getBoundBelCell(bel).c_str(ctx));
 -        topItem->addSubProperty(cellItem);
 +        QtProperty *topItem = addTopLevelProperty("Bel");
 -        QtVariantProperty *conflictItem = readOnlyManager->addProperty(QVariant::String, "Conflicting Cell");
 -        conflictItem->setValue(ctx->getConflictingBelCell(bel).c_str(ctx));
 -        topItem->addSubProperty(conflictItem);
 +        addProperty(topItem, QVariant::String, "Name", c.c_str(ctx));
 +        addProperty(topItem, QVariant::String, "Type", ctx->belTypeToId(ctx->getBelType(bel)).c_str(ctx));
 +        addProperty(topItem, QVariant::Bool, "Available", ctx->checkBelAvail(bel));
 +        addProperty(topItem, QVariant::String, "Bound Cell", ctx->getBoundBelCell(bel).c_str(ctx), ElementType::CELL);
 +        addProperty(topItem, QVariant::String, "Conflicting Cell", ctx->getConflictingBelCell(bel).c_str(ctx),
 +                    ElementType::CELL);
      } else if (type == ElementType::WIRE) {
          IdString c = static_cast<IdStringTreeItem *>(clickItem)->getData();
 @@ -374,76 +500,56 @@ void DesignWidget::onItemSelectionChanged()          decals.push_back(ctx->getWireDecal(wire));
          Q_EMIT selected(decals);
 -        QtProperty *topItem = groupManager->addProperty("Wire");
 -        addProperty(topItem, "Wire");
 -
 -        QtVariantProperty *nameItem = readOnlyManager->addProperty(QVariant::String, "Name");
 -        nameItem->setValue(c.c_str(ctx));
 -        topItem->addSubProperty(nameItem);
 -
 -        QtVariantProperty *availItem = readOnlyManager->addProperty(QVariant::Bool, "Available");
 -        availItem->setValue(ctx->checkWireAvail(wire));
 -        topItem->addSubProperty(availItem);
 +        QtProperty *topItem = addTopLevelProperty("Wire");
 -        QtVariantProperty *cellItem = readOnlyManager->addProperty(QVariant::String, "Bound Net");
 -        cellItem->setValue(ctx->getBoundWireNet(wire).c_str(ctx));
 -        topItem->addSubProperty(cellItem);
 -
 -        QtVariantProperty *conflictItem = readOnlyManager->addProperty(QVariant::String, "Conflicting Net");
 -        conflictItem->setValue(ctx->getConflictingWireNet(wire).c_str(ctx));
 -        topItem->addSubProperty(conflictItem);
 +        addProperty(topItem, QVariant::String, "Name", c.c_str(ctx));
 +        addProperty(topItem, QVariant::Bool, "Available", ctx->checkWireAvail(wire));
 +        addProperty(topItem, QVariant::String, "Bound Net", ctx->getBoundWireNet(wire).c_str(ctx), ElementType::NET);
 +        addProperty(topItem, QVariant::String, "Conflicting Net", ctx->getConflictingWireNet(wire).c_str(ctx),
 +                    ElementType::NET);
 +        QtProperty *belpinItem = addSubGroup(topItem, "BelPin Uphill");
          BelPin uphill = ctx->getBelPinUphill(wire);
 -        QtProperty *belpinItem = groupManager->addProperty("BelPin Uphill");
 -        topItem->addSubProperty(belpinItem);
 -
 -        QtVariantProperty *belUphillItem = readOnlyManager->addProperty(QVariant::String, "Bel");
          if (uphill.bel != BelId())
 -            belUphillItem->setValue(ctx->getBelName(uphill.bel).c_str(ctx));
 +            addProperty(belpinItem, QVariant::String, "Bel", ctx->getBelName(uphill.bel).c_str(ctx), ElementType::BEL);
          else
 -            belUphillItem->setValue("");
 -        belpinItem->addSubProperty(belUphillItem);
 +            addProperty(belpinItem, QVariant::String, "Bel", "", ElementType::BEL);
 -        QtVariantProperty *portUphillItem = readOnlyManager->addProperty(QVariant::String, "PortPin");
 -        portUphillItem->setValue(ctx->portPinToId(uphill.pin).c_str(ctx));
 -        belpinItem->addSubProperty(portUphillItem);
 +        addProperty(belpinItem, QVariant::String, "PortPin", ctx->portPinToId(uphill.pin).c_str(ctx), ElementType::BEL);
 -        QtProperty *downhillItem = groupManager->addProperty("BelPins Downhill");
 -        topItem->addSubProperty(downhillItem);
 +        QtProperty *downhillItem = addSubGroup(topItem, "BelPin Downhill");
          for (const auto &item : ctx->getBelPinsDownhill(wire)) {
              QString belname = "";
              if (item.bel != BelId())
                  belname = ctx->getBelName(item.bel).c_str(ctx);
              QString pinname = ctx->portPinToId(item.pin).c_str(ctx);
 -            QtProperty *dhItem = groupManager->addProperty(belname + "-" + pinname);
 -            downhillItem->addSubProperty(dhItem);
 -
 -            QtVariantProperty *belItem = readOnlyManager->addProperty(QVariant::String, "Bel");
 -            belItem->setValue(belname);
 -            dhItem->addSubProperty(belItem);
 -
 -            QtVariantProperty *portItem = readOnlyManager->addProperty(QVariant::String, "PortPin");
 -            portItem->setValue(pinname);
 -            dhItem->addSubProperty(portItem);
 +            QtProperty *dhItem = addSubGroup(downhillItem, belname + "-" + pinname);
 +            addProperty(dhItem, QVariant::String, "Bel", belname, ElementType::BEL);
 +            addProperty(dhItem, QVariant::String, "PortPin", pinname);
          }
 -/*
 -        QtProperty *pipsDownItem = groupManager->addProperty("Pips Downhill");
 -        topItem->addSubProperty(pipsDownItem);
 +
 +        int counter = 0;
 +        QtProperty *pipsDownItem = addSubGroup(downhillItem, "Pips Downhill");
          for (const auto &item : ctx->getPipsDownhill(wire)) {
 -            QtVariantProperty *pipItem = readOnlyManager->addProperty(QVariant::String, "");
 -            pipItem->setValue(ctx->getPipName(item).c_str(ctx));
 -            pipsDownItem->addSubProperty(pipItem);
 +            addProperty(pipsDownItem, QVariant::String, "", ctx->getPipName(item).c_str(ctx), ElementType::PIP);
 +            counter++;
 +            if (counter == 50) {
 +                addProperty(pipsDownItem, QVariant::String, "Warning", "Too many items...", ElementType::NONE);
 +                break;
 +            }
          }
 -        QtProperty *pipsUpItem = groupManager->addProperty("Pips Uphill");
 -        topItem->addSubProperty(pipsUpItem);
 +        counter = 0;
 +        QtProperty *pipsUpItem = addSubGroup(downhillItem, "Pips Uphill");
          for (const auto &item : ctx->getPipsUphill(wire)) {
 -            QtVariantProperty *pipItem = readOnlyManager->addProperty(QVariant::String, "");
 -            pipItem->setValue(ctx->getPipName(item).c_str(ctx));
 -            pipsUpItem->addSubProperty(pipItem);
 +            addProperty(pipsUpItem, QVariant::String, "", ctx->getPipName(item).c_str(ctx), ElementType::PIP);
 +            counter++;
 +            if (counter == 50) {
 +                addProperty(pipsUpItem, QVariant::String, "Warning", "Too many items...", ElementType::NONE);
 +                break;
 +            }
          }
 -*/
      } else if (type == ElementType::PIP) {
          IdString c = static_cast<IdStringTreeItem *>(clickItem)->getData();
          PipId pip = ctx->getPipByName(c);
 @@ -451,199 +557,108 @@ void DesignWidget::onItemSelectionChanged()          decals.push_back(ctx->getPipDecal(pip));
          Q_EMIT selected(decals);
 -        QtProperty *topItem = groupManager->addProperty("Pip");
 -        addProperty(topItem, "Pip");
 -
 -        QtVariantProperty *nameItem = readOnlyManager->addProperty(QVariant::String, "Name");
 -        nameItem->setValue(c.c_str(ctx));
 -        topItem->addSubProperty(nameItem);
 -
 -        QtVariantProperty *availItem = readOnlyManager->addProperty(QVariant::Bool, "Available");
 -        availItem->setValue(ctx->checkPipAvail(pip));
 -        topItem->addSubProperty(availItem);
 -
 -        QtVariantProperty *cellItem = readOnlyManager->addProperty(QVariant::String, "Bound Net");
 -        cellItem->setValue(ctx->getBoundPipNet(pip).c_str(ctx));
 -        topItem->addSubProperty(cellItem);
 +        QtProperty *topItem = addTopLevelProperty("Pip");
 -        QtVariantProperty *conflictItem = readOnlyManager->addProperty(QVariant::String, "Conflicting Net");
 -        conflictItem->setValue(ctx->getConflictingPipNet(pip).c_str(ctx));
 -        topItem->addSubProperty(conflictItem);
 -
 -        QtVariantProperty *srcWireItem = readOnlyManager->addProperty(QVariant::String, "Src Wire");
 -        srcWireItem->setValue(ctx->getWireName(ctx->getPipSrcWire(pip)).c_str(ctx));
 -        topItem->addSubProperty(srcWireItem);
 -
 -        QtVariantProperty *destWireItem = readOnlyManager->addProperty(QVariant::String, "Dest Wire");
 -        destWireItem->setValue(ctx->getWireName(ctx->getPipDstWire(pip)).c_str(ctx));
 -        topItem->addSubProperty(destWireItem);
 +        addProperty(topItem, QVariant::String, "Name", c.c_str(ctx));
 +        addProperty(topItem, QVariant::Bool, "Available", ctx->checkPipAvail(pip));
 +        addProperty(topItem, QVariant::String, "Bound Net", ctx->getBoundPipNet(pip).c_str(ctx), ElementType::NET);
 +        addProperty(topItem, QVariant::String, "Conflicting Net", ctx->getConflictingPipNet(pip).c_str(ctx),
 +                    ElementType::NET);
 +        addProperty(topItem, QVariant::String, "Src Wire", ctx->getWireName(ctx->getPipSrcWire(pip)).c_str(ctx),
 +                    ElementType::WIRE);
 +        addProperty(topItem, QVariant::String, "Dest Wire", ctx->getWireName(ctx->getPipDstWire(pip)).c_str(ctx),
 +                    ElementType::WIRE);
          DelayInfo delay = ctx->getPipDelay(pip);
 -        QtProperty *delayItem = groupManager->addProperty("Delay");
 -        topItem->addSubProperty(delayItem);
 -
 -        QtVariantProperty *raiseDelayItem = readOnlyManager->addProperty(QVariant::Double, "Raise");
 -        raiseDelayItem->setValue(delay.raiseDelay());
 -        delayItem->addSubProperty(raiseDelayItem);
 -
 -        QtVariantProperty *fallDelayItem = readOnlyManager->addProperty(QVariant::Double, "Fall");
 -        fallDelayItem->setValue(delay.fallDelay());
 -        delayItem->addSubProperty(fallDelayItem);
 -
 -        QtVariantProperty *avgDelayItem = readOnlyManager->addProperty(QVariant::Double, "Average");
 -        avgDelayItem->setValue(delay.avgDelay());
 -        delayItem->addSubProperty(avgDelayItem);
 +        QtProperty *delayItem = addSubGroup(topItem, "Delay");
 +        addProperty(delayItem, QVariant::Double, "Raise", delay.raiseDelay());
 +        addProperty(delayItem, QVariant::Double, "Fall", delay.fallDelay());
 +        addProperty(delayItem, QVariant::Double, "Average", delay.avgDelay());
      } else if (type == ElementType::NET) {
          IdString c = static_cast<IdStringTreeItem *>(clickItem)->getData();
          NetInfo *net = ctx->nets.at(c).get();
 -        QtProperty *topItem = groupManager->addProperty("Net");
 -        addProperty(topItem, "Net");
 -
 -        QtVariantProperty *nameItem = readOnlyManager->addProperty(QVariant::String, "Name");
 -        nameItem->setValue(net->name.c_str(ctx));
 -        topItem->addSubProperty(nameItem);
 -
 -        QtProperty *driverItem = groupManager->addProperty("Driver");
 -        topItem->addSubProperty(driverItem);
 -
 -        QtVariantProperty *portItem = readOnlyManager->addProperty(QVariant::String, "Port");
 -        portItem->setValue(net->driver.port.c_str(ctx));
 -        driverItem->addSubProperty(portItem);
 +        QtProperty *topItem = addTopLevelProperty("Net");
 -        QtVariantProperty *budgetItem = readOnlyManager->addProperty(QVariant::Double, "Budget");
 -        budgetItem->setValue(net->driver.budget);
 -        driverItem->addSubProperty(budgetItem);
 +        addProperty(topItem, QVariant::String, "Name", net->name.c_str(ctx));
 -        QtVariantProperty *cellNameItem = readOnlyManager->addProperty(QVariant::String, "Cell");
 +        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)
 -            cellNameItem->setValue(net->driver.cell->name.c_str(ctx));
 +            addProperty(driverItem, QVariant::String, "Cell", net->driver.cell->name.c_str(ctx), ElementType::CELL);
          else
 -            cellNameItem->setValue("");
 -        driverItem->addSubProperty(cellNameItem);
 +            addProperty(driverItem, QVariant::String, "Cell", "", ElementType::CELL);
 -        QtProperty *usersItem = groupManager->addProperty("Users");
 -        topItem->addSubProperty(usersItem);
 +        QtProperty *usersItem = addSubGroup(topItem, "Users");
          for (auto &item : net->users) {
 -            QtProperty *portItem = groupManager->addProperty(item.port.c_str(ctx));
 -            usersItem->addSubProperty(portItem);
 +            QtProperty *portItem = addSubGroup(usersItem, item.port.c_str(ctx));
 -            QtVariantProperty *nameItem = readOnlyManager->addProperty(QVariant::String, "Port");
 -            nameItem->setValue(item.port.c_str(ctx));
 -            portItem->addSubProperty(nameItem);
 -
 -            QtVariantProperty *budgetItem = readOnlyManager->addProperty(QVariant::Double, "Budget");
 -            budgetItem->setValue(item.budget);
 -            portItem->addSubProperty(budgetItem);
 -
 -            QtVariantProperty *userItem = readOnlyManager->addProperty(QVariant::String, "Cell");
 +            addProperty(portItem, QVariant::String, "Port", item.port.c_str(ctx));
 +            addProperty(portItem, QVariant::Double, "Budget", item.budget);
              if (item.cell)
 -                userItem->setValue(item.cell->name.c_str(ctx));
 +                addProperty(portItem, QVariant::String, "Cell", item.cell->name.c_str(ctx), ElementType::CELL);
              else
 -                userItem->setValue("");
 -            portItem->addSubProperty(userItem);
 +                addProperty(portItem, QVariant::String, "Cell", "", ElementType::CELL);
          }
 -        QtProperty *attrsItem = groupManager->addProperty("Attributes");
 -        topItem->addSubProperty(attrsItem);
 +        QtProperty *attrsItem = addSubGroup(topItem, "Attributes");
          for (auto &item : net->attrs) {
 -            QtVariantProperty *attrItem = readOnlyManager->addProperty(QVariant::String, item.first.c_str(ctx));
 -            attrItem->setValue(item.second.c_str());
 -            attrsItem->addSubProperty(attrItem);
 +            addProperty(attrsItem, QVariant::String, item.first.c_str(ctx), item.second.c_str());
          }
 -        QtProperty *wiresItem = groupManager->addProperty("Wires");
 -        topItem->addSubProperty(wiresItem);
 +        QtProperty *wiresItem = addSubGroup(topItem, "Wires");
          for (auto &item : net->wires) {
              auto name = ctx->getWireName(item.first).c_str(ctx);
 -            QtProperty *wireItem = groupManager->addProperty(name);
 -
 -            QtVariantProperty *nameItem = readOnlyManager->addProperty(QVariant::String, "Name");
 -            nameItem->setValue(name);
 -            wireItem->addSubProperty(nameItem);
 -
 -            QtVariantProperty *pipItem = readOnlyManager->addProperty(QVariant::String, "Pip");
 +            QtProperty *wireItem = addSubGroup(wiresItem, name);
 +            addProperty(wireItem, QVariant::String, "Name", name);
              if (item.second.pip != PipId())
 -                pipItem->setValue(ctx->getPipName(item.second.pip).c_str(ctx));
 +                addProperty(wireItem, QVariant::String, "Pip", ctx->getPipName(item.second.pip).c_str(ctx),
 +                            ElementType::PIP);
              else
 -                pipItem->setValue("");
 -            wireItem->addSubProperty(pipItem);
 +                addProperty(wireItem, QVariant::String, "Pip", "", ElementType::PIP);
 -            QtVariantProperty *strengthItem = readOnlyManager->addProperty(QVariant::Int, "Strength");
 -            strengthItem->setValue((int)item.second.strength);
 -            wireItem->addSubProperty(strengthItem);
 -
 -            wiresItem->addSubProperty(wireItem);
 +            addProperty(wireItem, QVariant::Int, "Strength", (int)item.second.strength);
          }
      } else if (type == ElementType::CELL) {
          IdString c = static_cast<IdStringTreeItem *>(clickItem)->getData();
          CellInfo *cell = ctx->cells.at(c).get();
 -        QtProperty *topItem = groupManager->addProperty("Cell");
 -        addProperty(topItem, "Cell");
 -
 -        QtVariantProperty *cellNameItem = readOnlyManager->addProperty(QVariant::String, "Name");
 -        cellNameItem->setValue(cell->name.c_str(ctx));
 -        topItem->addSubProperty(cellNameItem);
 -
 -        QtVariantProperty *cellTypeItem = readOnlyManager->addProperty(QVariant::String, "Type");
 -        cellTypeItem->setValue(cell->type.c_str(ctx));
 -        topItem->addSubProperty(cellTypeItem);
 +        QtProperty *topItem = addTopLevelProperty("Cell");
 -        QtVariantProperty *cellBelItem = readOnlyManager->addProperty(QVariant::String, "Bel");
 +        addProperty(topItem, QVariant::String, "Name", cell->name.c_str(ctx));
 +        addProperty(topItem, QVariant::String, "Type", cell->type.c_str(ctx));
          if (cell->bel != BelId())
 -            cellBelItem->setValue(ctx->getBelName(cell->bel).c_str(ctx));
 +            addProperty(topItem, QVariant::String, "Bel", ctx->getBelName(cell->bel).c_str(ctx), ElementType::BEL);
          else
 -            cellBelItem->setValue("");
 -        topItem->addSubProperty(cellBelItem);
 +            addProperty(topItem, QVariant::String, "Bel", "", ElementType::BEL);
 +        addProperty(topItem, QVariant::Int, "Bel strength", int(cell->belStrength));
 -        QtVariantProperty *cellBelStrItem = readOnlyManager->addProperty(QVariant::Int, "Bel strength");
 -        cellBelStrItem->setValue(int(cell->belStrength));
 -        topItem->addSubProperty(cellBelStrItem);
 -
 -        QtProperty *cellPortsItem = groupManager->addProperty("Ports");
 -        topItem->addSubProperty(cellPortsItem);
 +        QtProperty *cellPortsItem = addSubGroup(topItem, "Ports");
          for (auto &item : cell->ports) {
              PortInfo p = item.second;
 -            QtProperty *portInfoItem = groupManager->addProperty(p.name.c_str(ctx));
 -
 -            QtVariantProperty *portInfoNameItem = readOnlyManager->addProperty(QVariant::String, "Name");
 -            portInfoNameItem->setValue(p.name.c_str(ctx));
 -            portInfoItem->addSubProperty(portInfoNameItem);
 -
 -            QtVariantProperty *portInfoTypeItem = readOnlyManager->addProperty(QVariant::Int, "Type");
 -            portInfoTypeItem->setValue(int(p.type));
 -            portInfoItem->addSubProperty(portInfoTypeItem);
 -
 -            QtVariantProperty *portInfoNetItem = readOnlyManager->addProperty(QVariant::String, "Net");
 +            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)
 -                portInfoNetItem->setValue(p.net->name.c_str(ctx));
 +                addProperty(portInfoItem, QVariant::String, "Net", p.net->name.c_str(ctx), ElementType::NET);
              else
 -                portInfoNetItem->setValue("");
 -            portInfoItem->addSubProperty(portInfoNetItem);
 -
 -            cellPortsItem->addSubProperty(portInfoItem);
 +                addProperty(portInfoItem, QVariant::String, "Net", "", ElementType::NET);
          }
 -        QtProperty *cellAttrItem = groupManager->addProperty("Attributes");
 -        topItem->addSubProperty(cellAttrItem);
 +        QtProperty *cellAttrItem = addSubGroup(topItem, "Attributes");
          for (auto &item : cell->attrs) {
 -            QtVariantProperty *attrItem = readOnlyManager->addProperty(QVariant::String, item.first.c_str(ctx));
 -            attrItem->setValue(item.second.c_str());
 -            cellAttrItem->addSubProperty(attrItem);
 +            addProperty(cellAttrItem, QVariant::String, item.first.c_str(ctx), item.second.c_str());
          }
 -        QtProperty *cellParamsItem = groupManager->addProperty("Parameters");
 -        topItem->addSubProperty(cellParamsItem);
 +        QtProperty *cellParamsItem = addSubGroup(topItem, "Parameters");
          for (auto &item : cell->params) {
 -            QtVariantProperty *paramItem = readOnlyManager->addProperty(QVariant::String, item.first.c_str(ctx));
 -            paramItem->setValue(item.second.c_str());
 -            cellParamsItem->addSubProperty(paramItem);
 +            addProperty(cellParamsItem, QVariant::String, item.first.c_str(ctx), item.second.c_str());
          }
          QtProperty *cellPinsItem = groupManager->addProperty("Pins");
 @@ -652,38 +667,159 @@ void DesignWidget::onItemSelectionChanged()              std::string cell_port = item.first.c_str(ctx);
              std::string bel_pin = item.second.c_str(ctx);
 -            QtProperty *pinGroupItem = groupManager->addProperty((cell_port + " -> " + bel_pin).c_str());
 +            QtProperty *pinGroupItem = addSubGroup(cellPortsItem, (cell_port + " -> " + bel_pin).c_str());
 +
 +            addProperty(pinGroupItem, QVariant::String, "Cell", cell_port.c_str(), ElementType::CELL);
 +            addProperty(pinGroupItem, QVariant::String, "Bel", bel_pin.c_str(), ElementType::BEL);
 +        }
 +    }
 +}
 +
 +std::vector<DecalXY> DesignWidget::getDecals(ElementType type, IdString value)
 +{
 +    std::vector<DecalXY> decals;
 +    switch (type) {
 +    case ElementType::BEL: {
 +        BelId bel = ctx->getBelByName(value);
 +        if (bel != BelId()) {
 +            decals.push_back(ctx->getBelDecal(bel));
 +        }
 +    } break;
 +    case ElementType::WIRE: {
 +        WireId wire = ctx->getWireByName(value);
 +        if (wire != WireId()) {
 +            decals.push_back(ctx->getWireDecal(wire));
 +            Q_EMIT selected(decals);
 +        }
 +    } break;
 +    case ElementType::PIP: {
 +        PipId pip = ctx->getPipByName(value);
 +        if (pip != PipId()) {
 +            decals.push_back(ctx->getPipDecal(pip));
 +            Q_EMIT selected(decals);
 +        }
 +    } break;
 +    case ElementType::NET: {
 +    } break;
 +    case ElementType::CELL: {
 +    } break;
 +    default:
 +        break;
 +    }
 +    return decals;
 +}
 -            QtVariantProperty *cellItem = readOnlyManager->addProperty(QVariant::String, "Cell");
 -            cellItem->setValue(cell_port.c_str());
 -            pinGroupItem->addSubProperty(cellItem);
 +void DesignWidget::updateHighlightGroup(QTreeWidgetItem *item, int group)
 +{
 +    if (highlightSelected.contains(item)) {
 +        if (highlightSelected[item] == group) {
 +            highlightSelected.remove(item);
 +        } else
 +            highlightSelected[item] = group;
 +    } else
 +        highlightSelected.insert(item, group);
 -            QtVariantProperty *belItem = readOnlyManager->addProperty(QVariant::String, "Bel");
 -            belItem->setValue(bel_pin.c_str());
 -            pinGroupItem->addSubProperty(belItem);
 +    std::vector<DecalXY> decals;
 -            cellPinsItem->addSubProperty(pinGroupItem);
 +    for (auto it : highlightSelected.toStdMap()) {
 +        if (it.second == group) {
 +            ElementType type = static_cast<ElementTreeItem *>(it.first)->getType();
 +            IdString value = static_cast<IdStringTreeItem *>(it.first)->getData();
 +            std::vector<DecalXY> d = getDecals(type, value);
 +            std::move(d.begin(), d.end(), std::back_inserter(decals));
          }
      }
 +
 +    Q_EMIT highlight(decals, group);
  }
 -void DesignWidget::prepareMenu(const QPoint &pos)
 +void DesignWidget::prepareMenuProperty(const QPoint &pos)
  {
 -    QTreeWidget *tree = treeWidget;
 +    QTreeWidget *tree = propertyEditor->treeWidget();
      itemContextMenu = tree->itemAt(pos);
 +    if (itemContextMenu->parent() == nullptr)
 +        return;
 -    QAction *selectAction = new QAction("&Select", this);
 -    selectAction->setStatusTip("Select item on view");
 +    QtBrowserItem *browserItem = propertyEditor->itemToBrowserItem(itemContextMenu);
 +    if (!browserItem)
 +        return;
 +    QtProperty *selectedProperty = browserItem->property();
 +    ElementType type = getElementTypeByName(selectedProperty->propertyId());
 +    if (type == ElementType::NONE)
 +        return;
 +    IdString value = ctx->id(selectedProperty->valueText().toStdString());
 -    connect(selectAction, SIGNAL(triggered()), this, SLOT(selectObject()));
 +    QTreeWidgetItem *item = nameToItem[getElementIndex(type)].value(value.c_str(ctx));
      QMenu menu(this);
 +    QAction *selectAction = new QAction("&Select", this);
 +    connect(selectAction, &QAction::triggered, this, [this, type, value] { Q_EMIT selected(getDecals(type, value)); });
      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 (highlightSelected.contains(item) && highlightSelected[item] == i)
 +            action->setChecked(true);
 +        connect(action, &QAction::triggered, this, [this, i, item] { updateHighlightGroup(item, i); });
 +    }
 +    menu.exec(tree->mapToGlobal(pos));
 +}
 +
 +void DesignWidget::prepareMenuTree(const QPoint &pos)
 +{
 +    QTreeWidget *tree = treeWidget;
 +
 +    itemContextMenu = tree->itemAt(pos);
 +
 +    ElementType type = static_cast<ElementTreeItem *>(itemContextMenu)->getType();
 +    IdString value = static_cast<IdStringTreeItem *>(itemContextMenu)->getData();
 +
 +    if (type == ElementType::NONE)
 +        return;
 +
 +    QTreeWidgetItem *item = nameToItem[getElementIndex(type)].value(value.c_str(ctx));
 +
 +    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 (highlightSelected.contains(item) && highlightSelected[item] == i)
 +            action->setChecked(true);
 +        connect(action, &QAction::triggered, this, [this, i, item] { updateHighlightGroup(item, i); });
 +    }
      menu.exec(tree->mapToGlobal(pos));
  }
 -void DesignWidget::selectObject() { Q_EMIT info("selected " + itemContextMenu->text(0).toStdString() + "\n"); }
 +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);
 +    switch (type) {
 +    case ElementType::NONE:
 +        return;
 +    default: {
 +        if (nameToItem[index].contains(value))
 +            treeWidget->setCurrentItem(nameToItem[index].value(value));
 +    } break;
 +    }
 +}
  NEXTPNR_NAMESPACE_END
  | 
