#include "dbcmaineditor.h" #include "ui_dbcmaineditor.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "helpwindow.h" DBCMainEditor::DBCMainEditor( const QVector *frames, QWidget *parent) : QDialog(parent), ui(new Ui::DBCMainEditor) { ui->setupUi(this); readSettings(); dbcHandler = DBCHandler::getReference(); referenceFrames = frames; ui->treeDBC->setContextMenuPolicy(Qt::CustomContextMenu); connect(ui->btnSearch, &QAbstractButton::clicked, this, &DBCMainEditor::handleSearch); connect(ui->lineSearch, &QLineEdit::returnPressed, this, &DBCMainEditor::handleSearch); connect(ui->btnSearchNext, &QAbstractButton::clicked, this, &DBCMainEditor::handleSearchForward); connect(ui->btnSearchPrev, &QAbstractButton::clicked, this, &DBCMainEditor::handleSearchBackward); connect(ui->treeDBC, &QTreeWidget::doubleClicked, this, &DBCMainEditor::onTreeDoubleClicked); connect(ui->treeDBC, &QTreeWidget::customContextMenuRequested, this, &DBCMainEditor::onTreeContextMenu); connect(ui->treeDBC, &QTreeWidget::currentItemChanged, this, &DBCMainEditor::currentItemChanged); connect(ui->btnDelete, &QAbstractButton::clicked, this, &DBCMainEditor::deleteCurrentTreeItem); connect(ui->btnNewNode, &QAbstractButton::clicked, this, QOverload<>::of(&DBCMainEditor::newNode)); connect(ui->btnNewMessage, &QAbstractButton::clicked, this, &DBCMainEditor::newMessage); connect(ui->btnNewSignal, &QAbstractButton::clicked, this, &DBCMainEditor::newSignal); connect(ui->btnUsedIDs, &QAbstractButton::clicked, this, &DBCMainEditor::showUsedIDs); sigEditor = new DBCSignalEditor(this); msgEditor = new DBCMessageEditor(this); nodeEditor = new DBCNodeEditor(this); nodeRebaseEditor = new DBCNodeRebaseEditor(this); nodeDuplicateEditor = new DBCNodeDuplicateEditor(this); //all three might potentially change the data stored and force the tree to be updated connect(sigEditor, &DBCSignalEditor::updatedTreeInfo, this, &DBCMainEditor::updatedSignal); connect(msgEditor, &DBCMessageEditor::updatedTreeInfo, this, &DBCMainEditor::updatedMessage); connect(nodeEditor, &DBCNodeEditor::updatedTreeInfo, this, &DBCMainEditor::updatedNode); connect(nodeRebaseEditor, &DBCNodeRebaseEditor::updatedTreeInfo, this, &DBCMainEditor::updatedMessage); connect(nodeDuplicateEditor, &DBCNodeDuplicateEditor::updatedTreeInfo, this, &DBCMainEditor::updatedMessage); connect(nodeDuplicateEditor, &DBCNodeDuplicateEditor::createNode, this, QOverload::of(&DBCMainEditor::newNode)); connect(nodeDuplicateEditor, &DBCNodeDuplicateEditor::cloneMessageToNode, this, &DBCMainEditor::copyMessageToNode); connect(nodeDuplicateEditor, &DBCNodeDuplicateEditor::nodeAdded, this, &DBCMainEditor::refreshTree); nodeIcon = QIcon(":/icons/images/node.png"); messageIcon = QIcon(":/icons/images/message.png"); signalIcon = QIcon(":/icons/images/signal.png"); multiplexedSignalIcon = QIcon(":/icons/images/multiplexed-signal.png"); multiplexorSignalIcon = QIcon(":/icons/images/multiplexor-signal.png"); //ui->btnDelete->setFixedSize(32,32); ui->btnDelete->setIconSize(QSize(32, 32)); //ui->btnNewNode->setFixedSize(32,32); ui->btnNewNode->setIconSize(QSize(32, 32)); //ui->btnNewMessage->setFixedSize(32,32); ui->btnNewMessage->setIconSize(QSize(32, 32)); //ui->btnNewSignal->setFixedSize(32,32); ui->btnNewSignal->setIconSize(QSize(32, 32)); ui->btnUsedIDs->setIconSize(QSize(32, 32)); installEventFilter(this); } void DBCMainEditor::showEvent(QShowEvent* event) { QDialog::showEvent(event); refreshTree(); } DBCMainEditor::~DBCMainEditor() { removeEventFilter(this); delete ui; delete sigEditor; delete msgEditor; delete nodeEditor; } bool DBCMainEditor::eventFilter(QObject *obj, QEvent *event) { if (event->type() == QEvent::KeyRelease) { QKeyEvent *keyEvent = static_cast(event); switch (keyEvent->key()) { case Qt::Key_F1: HelpWindow::getRef()->showHelp("dbc_editor.md"); break; case Qt::Key_F3: handleSearchForward(); break; case Qt::Key_F4: handleSearchBackward(); break; case Qt::Key_F5: newNode(); break; case Qt::Key_F6: newMessage(); break; case Qt::Key_F7: newSignal(); break; case Qt::Key_Delete: deleteCurrentTreeItem(); } return true; } else { // standard event processing return QObject::eventFilter(obj, event); } return false; } void DBCMainEditor::setFileIdx(int idx) { if (idx < 0 || idx > dbcHandler->getFileCount() - 1) return; dbcFile = dbcHandler->getFileByIdx(idx); fileIdx = idx; } void DBCMainEditor::closeEvent(QCloseEvent *event) { Q_UNUSED(event) writeSettings(); sigEditor->close(); } void DBCMainEditor::readSettings() { QSettings settings; if (settings.value("Main/SaveRestorePositions", false).toBool()) { resize(settings.value("DBCMainEditor/WindowSize", QSize(1103, 571)).toSize()); move(Utility::constrainedWindowPos(settings.value("DBCMainEditor/WindowPos", QPoint(50, 50)).toPoint())); } } void DBCMainEditor::writeSettings() { QSettings settings; if (settings.value("Main/SaveRestorePositions", false).toBool()) { settings.setValue("DBCMainEditor/WindowSize", size()); settings.setValue("DBCMainEditor/WindowPos", pos()); } } void DBCMainEditor::onCustomMenuTree(QPoint point) { Q_UNUSED(point); QMenu *menu = new QMenu(this); menu->setAttribute(Qt::WA_DeleteOnClose); menu->addAction(tr("Delete currently selected message"), this, SLOT(deleteCurrentMessage())); //menu->popup(ui->MessagesTable->mapToGlobal(point)); } void DBCMainEditor::handleSearch() { searchItems = ui->treeDBC->findItems(ui->lineSearch->text(),Qt::MatchContains | Qt::MatchRecursive); qDebug() << "Search returned " << searchItems.count() << "items."; if (searchItems.count() > 0) { ui->treeDBC->setCurrentItem(searchItems[0]); searchItemPos = 0; ui->lblSearchPos->setText("Search Results: " + QString::number(searchItemPos + 1) + " of " + QString::number(searchItems.count())); } else { ui->lblSearchPos->setText("Search Results: 0 of 0"); } } void DBCMainEditor::handleSearchForward() { if (searchItems.count() == 0) return; if (searchItemPos < searchItems.count() - 1) searchItemPos++; else searchItemPos = 0; ui->treeDBC->setCurrentItem(searchItems[searchItemPos]); ui->lblSearchPos->setText("Search Results: " + QString::number(searchItemPos + 1) + " of " + QString::number(searchItems.count())); } void DBCMainEditor::handleSearchBackward() { if (searchItems.count() == 0) return; if (searchItemPos > 0) searchItemPos--; else searchItemPos = searchItems.count() - 1; ui->treeDBC->setCurrentItem(searchItems[searchItemPos]); ui->lblSearchPos->setText("Search Results: " + QString::number(searchItemPos + 1) + " of " + QString::number(searchItems.count())); } void DBCMainEditor::currentItemChanged(QTreeWidgetItem *current, QTreeWidgetItem *prev) { Q_UNUSED(prev) if (!current) { ui->btnNewMessage->setEnabled(false); ui->btnNewSignal->setEnabled(false); ui->btnDelete->setEnabled(false); return; } switch (current->data(0, Qt::UserRole).toInt()) { case DBCItemTypes::NODE: //node ui->btnNewNode->setEnabled(true); ui->btnNewMessage->setEnabled(true); ui->btnNewSignal->setEnabled(false); ui->btnDelete->setEnabled(true); break; case DBCItemTypes::MESG: //message ui->btnNewNode->setEnabled(true); ui->btnNewMessage->setEnabled(true); ui->btnNewSignal->setEnabled(true); ui->btnDelete->setEnabled(true); break; case DBCItemTypes::SIG: //signal ui->btnNewNode->setEnabled(true); ui->btnNewMessage->setEnabled(true); ui->btnNewSignal->setEnabled(true); ui->btnDelete->setEnabled(true); break; default: ui->btnNewMessage->setEnabled(false); ui->btnNewSignal->setEnabled(false); ui->btnDelete->setEnabled(false); } } uint32_t DBCMainEditor::getParentMessageID(QTreeWidgetItem *cell) { if (cell->data(0, Qt::UserRole) == DBCItemTypes::MESG) { return static_cast(Utility::ParseStringToNum(cell->text(0).split(" ")[0])); } else { if (cell->parent()) return getParentMessageID(cell->parent()); else return 0; } } //Double clicking is interpreted as a desire to edit the given item. void DBCMainEditor::onTreeDoubleClicked(const QModelIndex &index) { Q_UNUSED(index) QTreeWidgetItem* firstCol = ui->treeDBC->currentItem(); //bool ret = false; DBC_MESSAGE *msg; DBC_SIGNAL *sig; DBC_NODE *node; uint32_t msgID; QString idString; qDebug() << firstCol->data(0, Qt::UserRole) << " - " << firstCol->text(0); switch (firstCol->data(0, Qt::UserRole).toInt()) { case DBCItemTypes::NODE: //a node idString = firstCol->text(0).split(" ")[0]; node = dbcFile->findNodeByName(idString); nodeEditor->setFileIdx(fileIdx); nodeEditor->setNodeRef(node); nodeEditor->refreshView(); nodeEditor->show(); break; case DBCItemTypes::MESG: //a message idString = firstCol->text(0).split(" ")[0]; msgID = static_cast(Utility::ParseStringToNum(idString)); msg = dbcFile->messageHandler->findMsgByID(msgID); msgEditor->setMessageRef(msg); msgEditor->setFileIdx(fileIdx); //msgEditor->setWindowModality(Qt::WindowModal); msgEditor->refreshView(); msgEditor->show(); //show allows the rest of the forms to keep going break; case DBCItemTypes::SIG: //a signal msgID = getParentMessageID(firstCol); msg = dbcFile->messageHandler->findMsgByID(msgID); QString nameString = firstCol->text(0); if (nameString.contains("(")) { nameString = nameString.split(")")[1].trimmed(); //remove (1-2) type stuff from beginning of string } nameString = nameString.split(" ")[0]; //get rid of [32m 8] type stuff after the name sig = msg->sigHandler->findSignalByName(nameString); if (sig) { sigEditor->setSignalRef(sig); sigEditor->setMessageRef(msg); sigEditor->setFileIdx(fileIdx); //sigEditor->setWindowModality(Qt::WindowModal); sigEditor->refreshView(); sigEditor->show(); } break; } } void DBCMainEditor::onTreeContextMenu(const QPoint & pos) { QTreeWidgetItem* firstCol = ui->treeDBC->currentItem(); QString idString; qDebug() << firstCol->data(0, Qt::UserRole) << " - " << firstCol->text(0); switch (firstCol->data(0, Qt::UserRole).toInt()) { case DBCItemTypes::NODE: //a node idString = firstCol->text(0).split(" ")[0]; //node = dbcFile->findNodeByName(idString); QAction *actionRebase = new QAction(QIcon(":/Resource/warning32.ico"), tr("Rebase all messages"), this); actionRebase->setStatusTip(tr("Rebase all messages in node")); connect(actionRebase, SIGNAL(triggered()), this, SLOT(onRebaseMessages())); QAction *actionDupe = new QAction(QIcon(":/Resource/warning32.ico"), tr("Duplicate node"), this); actionDupe->setStatusTip(tr("Duplicate node and messages")); connect(actionDupe, SIGNAL(triggered()), this, SLOT(onDuplicateNode())); QMenu menu(this); menu.addAction(actionRebase); menu.addAction(actionDupe); //QPoint pt(pos); menu.exec( ui->treeDBC->mapToGlobal(pos) ); break; } } void DBCMainEditor::onRebaseMessages() { QTreeWidgetItem* firstCol = ui->treeDBC->currentItem(); DBC_NODE *node; QString idString; idString = firstCol->text(0).split(" ")[0]; node = dbcFile->findNodeByName(idString); nodeRebaseEditor->setFileIdx(fileIdx); nodeRebaseEditor->setNodeRef(node); if(nodeRebaseEditor->refreshView()) { nodeRebaseEditor->setModal(true); nodeRebaseEditor->show(); } } void DBCMainEditor::onDuplicateNode() { QTreeWidgetItem* firstCol = ui->treeDBC->currentItem(); //bool ret = false; //DBC_MESSAGE *msg; //DBC_SIGNAL *sig; //uint32_t msgID; DBC_NODE *node; QString idString; idString = firstCol->text(0).split(" ")[0]; node = dbcFile->findNodeByName(idString); nodeDuplicateEditor->setFileIdx(fileIdx); nodeDuplicateEditor->setNodeRef(node); if(nodeDuplicateEditor->refreshView()) { nodeDuplicateEditor->setModal(true); nodeDuplicateEditor->show(); } } /* * Recreate the whole tree with pretty icons and custom user roles that give the rest of code an easy way to figure out whether a given tree node * is a node, message, or signal. */ void DBCMainEditor::refreshTree() { ui->treeDBC->clear(); nodeToItem.clear(); messageToItem.clear(); signalToItem.clear(); itemToNode.clear(); itemToMessage.clear(); itemToSignal.clear(); if (dbcFile->findNodeByName("Vector__XXX") == nullptr) { DBC_NODE newNode; newNode.name = "Vector__XXX"; newNode.comment = "Default node if no other node is specified"; dbcFile->dbc_nodes.append(newNode); } for (int n = 0; n < dbcFile->dbc_nodes.count(); n++) { DBC_NODE *node = &dbcFile->dbc_nodes[n]; QTreeWidgetItem *nodeItem = new QTreeWidgetItem(); QString nodeInfo = node->name; if (node->comment.count() > 0) nodeInfo.append(" - ").append(node->comment); nodeItem->setText(0, nodeInfo); nodeItem->setIcon(0, nodeIcon); nodeItem->setData(0, Qt::UserRole, DBCItemTypes::NODE); nodeToItem.insert(node, nodeItem); itemToNode.insert(nodeItem, node); for (int x = 0; x < dbcFile->messageHandler->getCount(); x++) { DBC_MESSAGE *msg = dbcFile->messageHandler->findMsgByIdx(x); if (msg->sender->name == node->name) { QTreeWidgetItem *msgItem = new QTreeWidgetItem(nodeItem); QString msgInfo = Utility::formatCANID(msg->ID) + " " + msg->name; if (msg->comment.count() > 0) msgInfo.append(" - ").append(msg->comment); msgItem->setText(0, msgInfo); msgItem->setIcon(0, messageIcon); msgItem->setData(0, Qt::UserRole, DBCItemTypes::MESG); messageToItem.insert(msg, msgItem); itemToMessage.insert(msgItem, msg); for (int i = 0; i < msg->sigHandler->getCount(); i++) { DBC_SIGNAL *sig = msg->sigHandler->findSignalByIdx(i); //only process signals here which are "top" level if (sig->multiplexParent == nullptr) processSignalToTree(msgItem, sig); } } } ui->treeDBC->addTopLevelItem(nodeItem); } ui->treeDBC->sortItems(0, Qt::SortOrder::AscendingOrder); //sort the display list for ease in viewing by mere mortals, helps me a lot. } QString DBCMainEditor::createSignalText(DBC_SIGNAL *sig) { QString sigInfo; if (sig->isMultiplexed) { sigInfo = "(" + sig->multiplexDbcString() + ") "; } sigInfo.append(sig->name); if (sig->intelByteOrder) sigInfo.append(" [" + QString::number(sig->startBit) + "i " + QString::number(sig->signalSize) + "]"); else sigInfo.append(" [" + QString::number(sig->startBit) + "m " + QString::number(sig->signalSize) + "]"); if (sig->comment.count() > 0) sigInfo.append(" - ").append(sig->comment); return sigInfo; } //Signals can have a hierarchial relationship with other signals so this function is separate and calls itself recursively to build the tree void DBCMainEditor::processSignalToTree(QTreeWidgetItem *parent, DBC_SIGNAL *sig) { QTreeWidgetSignalItem *sigItem = new QTreeWidgetSignalItem(parent); QString sigInfo = createSignalText(sig); sigItem->setText(0, sigInfo); if (sig->isMultiplexor) sigItem->setIcon(0, multiplexorSignalIcon); else if (sig->isMultiplexed) sigItem->setIcon(0, multiplexedSignalIcon); else sigItem->setIcon(0, signalIcon); sigItem->setData(0, Qt::UserRole, DBCItemTypes::SIG); signalToItem.insert(sig, sigItem); itemToSignal.insert(sigItem, sig); if (sig->multiplexedChildren.count() > 0) { for (int i = 0; i < sig->multiplexedChildren.count(); i++) { processSignalToTree(sigItem, sig->multiplexedChildren[i]); } } } void DBCMainEditor::updatedNode(DBC_NODE *node) { if (nodeToItem.contains(node)) { QTreeWidgetItem *item = nodeToItem[node]; QString nodeInfo = node->name; if (node->comment.count() > 0) nodeInfo.append(" - ").append(node->comment); item->setText(0, nodeInfo); } else qDebug() << "That node doesn't exist. That's a bug dude."; } void DBCMainEditor::updatedMessage(DBC_MESSAGE *msg) { if (messageToItem.contains(msg)) { QTreeWidgetItem *item = messageToItem.value(msg); QString msgInfo = Utility::formatCANID(msg->ID) + " " + msg->name; if (msg->comment.count() > 0) msgInfo.append(" - ").append(msg->comment); item->setText(0, msgInfo); //editor could have changed the parent Node too. Have to figure out which node //is parent in the GUI and compare that to parent in the data. DBC_NODE *oldParent = dbcFile->findNodeByName(item->parent()->text(0).split(" - ")[0]); if (oldParent != msg->sender && oldParent) { qDebug() << "Changed parent of message. Trying to rehome it."; QTreeWidgetItem *newParent = nullptr; if (nodeToItem.contains(msg->sender)) newParent = nodeToItem.value(msg->sender); else //new node created within message editor. Create the item and then use it { QTreeWidgetItem *newNodeItem = new QTreeWidgetItem(); newNodeItem->setText(0, msg->sender->name); newNodeItem->setIcon(0, nodeIcon); newNodeItem->setData(0, Qt::UserRole, DBCItemTypes::NODE); ui->treeDBC->addTopLevelItem(newNodeItem); nodeToItem.insert(msg->sender, newNodeItem); itemToNode.insert(newNodeItem, msg->sender); newParent = newNodeItem; } QTreeWidgetItem *prevParent = nodeToItem.value(oldParent); prevParent->removeChild(item); newParent->addChild(item); ui->treeDBC->setCurrentItem(item); ui->treeDBC->sortItems(0, Qt::AscendingOrder); //resort because we just moved an item } } else qDebug() << "That mesage doesn't exist. That's a bug dude."; } void DBCMainEditor::updatedSignal(DBC_SIGNAL *sig) { if (signalToItem.contains(sig)) { QTreeWidgetItem *item = signalToItem.value(sig); QString sigInfo = createSignalText(sig); item->setText(0, sigInfo); if (sig->isMultiplexed) { if (item->parent()->data(0, Qt::UserRole).toInt() == DBCItemTypes::SIG) //if our parent is another signal { QString nameString = item->parent()->text(0); if (nameString.contains("(")) nameString = nameString.split(" ")[1]; else nameString = nameString.split(" ")[0]; DBC_SIGNAL *oldParent = sig->parentMessage->sigHandler->findSignalByName(nameString); if (oldParent && (oldParent != sig->multiplexParent)) { qDebug() << "You changed the signal's parent"; QTreeWidgetItem *newParent = nullptr; newParent = signalToItem.value(sig->multiplexParent); QTreeWidgetItem *prevParent = signalToItem.value(oldParent); prevParent->removeChild(item); newParent->addChild(item); ui->treeDBC->setCurrentItem(item); ui->treeDBC->sortItems(0, Qt::AscendingOrder); //resort because we just moved an item } } } } else qDebug() << "That signal doesn't exist. That's a bug dude."; } void DBCMainEditor::newNode(QString nodeName) { DBC_NODE node; DBC_NODE *nodePtr; if(nodeName.isEmpty()) { node.name = "Unnamed" + QString::number(randGen.bounded(50000)); } else { node.name = nodeName; } dbcFile->dbc_nodes.append(node); nodePtr = dbcFile->findNodeByName(node.name); QTreeWidgetItem *nodeItem = new QTreeWidgetItem(); nodeItem->setText(0, node.name); nodeItem->setIcon(0, nodeIcon); nodeItem->setData(0, Qt::UserRole, DBCItemTypes::NODE); nodeToItem.insert(nodePtr, nodeItem); itemToNode.insert(nodeItem, nodePtr); ui->treeDBC->addTopLevelItem(nodeItem); ui->treeDBC->setCurrentItem(nodeItem); dbcFile->setDirtyFlag(); } void DBCMainEditor::newNode() { newNode(QString()); } void DBCMainEditor::copyMessageToNode(DBC_NODE *parentNode, DBC_MESSAGE *source, uint newMsgId) { DBC_NODE *node = parentNode; if (!node) node = dbcFile->findNodeByIdx(0); QTreeWidgetItem *nodeItem = nullptr; DBC_MESSAGE msg; DBC_MESSAGE *msgPtr; nodeItem = ui->treeDBC->currentItem(); msg.name = source->name; msg.ID = newMsgId; msg.len = source->len; msg.bgColor = source->bgColor; msg.fgColor = source->fgColor; msg.comment = source->comment; msg.sender = node; DBC_SIGNAL *sigSource; int sigCount = source->sigHandler->getCount(); for(int i=0; isigHandler->findSignalByIdx(i); DBC_SIGNAL sig; //Does not properly handle multiplexed signals, for now sig.name = sigSource->name; sig.bias = sigSource->bias; sig.isMultiplexed = false; //sigSource->isMultiplexed; sig.isMultiplexor = false; //sigSource->isMultiplexor; sig.max = sigSource->max; sig.min = sigSource->min; sig.copyMultiplexValuesFromSignal(*sigSource); sig.factor = sigSource->factor; sig.intelByteOrder = sigSource->intelByteOrder; sig.parentMessage = &msg; sig.multiplexParent = nullptr; //need to learn about multiplexed signals and track them when copying sig.receiver = node; sig.signalSize = sigSource->signalSize; sig.startBit = sigSource->startBit; sig.valType = sigSource->valType; sig.parentMessage = &msg; msg.sigHandler->addSignal(sig); } msg.sigHandler->sort(); dbcFile->messageHandler->addMessage(msg); msgPtr = dbcFile->messageHandler->findMsgByIdx(dbcFile->messageHandler->getCount() - 1); QTreeWidgetItem *newMsgItem = new QTreeWidgetItem(); QString msgInfo = Utility::formatCANID(msg.ID) + " " + msg.name; if (msg.comment.count() > 0) msgInfo.append(" - ").append(msg.comment); newMsgItem->setText(0, msgInfo); newMsgItem->setIcon(0, messageIcon); newMsgItem->setData(0, Qt::UserRole, DBCItemTypes::MESG); messageToItem.insert(msgPtr, newMsgItem); itemToMessage.insert(newMsgItem, msgPtr); nodeItem->addChild(newMsgItem); //ui->treeDBC->setCurrentItem(newMsgItem); dbcFile->setDirtyFlag(); } //create a new message with it's parent being the node we're currently within void DBCMainEditor::newMessage() { QTreeWidgetItem *nodeItem = nullptr; QTreeWidgetItem *msgItem = nullptr; nodeItem = ui->treeDBC->currentItem(); int typ = nodeItem->data(0, Qt::UserRole).toInt(); if (!nodeItem) return; //nothing selected! if (typ == DBCItemTypes::MESG) { msgItem = nodeItem; nodeItem = nodeItem->parent(); } if (typ == DBCItemTypes::SIG) { msgItem = nodeItem->parent(); nodeItem = msgItem->parent(); } if (typ == DBCItemTypes::NODE){ msgItem = nodeItem; } //if there was a comment this will find the location of the comment and snip it out. QString nodeName = nodeItem->data(0, Qt::DisplayRole).toString().split(" - ")[0]; DBC_NODE *node = dbcFile->findNodeByName(nodeName); if (!node) node = dbcFile->findNodeByIdx(0); DBC_MESSAGE msg; DBC_MESSAGE *msgPtr; if (msgItem) { QString idString = msgItem->text(0).split(" ")[0]; int msgID = static_cast(Utility::ParseStringToNum(idString)); DBC_MESSAGE *oldMsg = dbcFile->messageHandler->findMsgByID(msgID); if (oldMsg) { msg.name = "Msg" + QString::number(randGen.bounded(500)); msg.ID = oldMsg->ID + 1; DBC_MESSAGE *overlappedMsg = dbcFile->messageHandler->findMsgByID(msg.ID); while (overlappedMsg) { msg.ID++; overlappedMsg = dbcFile->messageHandler->findMsgByID(msg.ID); } msg.len = oldMsg->len; msg.bgColor = oldMsg->bgColor; msg.fgColor = oldMsg->fgColor; msg.comment = oldMsg->comment; } else { msg.name = nodeName + "Msg" + QString::number(randGen.bounded(500)); msg.ID = 0; msg.len = 8; msg.bgColor = QApplication::palette().color(QPalette::Base); } } else { msg.name = nodeName + "Msg" + QString::number(randGen.bounded(500)); msg.ID = 0; msg.len = 8; } msg.sender = node; dbcFile->messageHandler->addMessage(msg); msgPtr = dbcFile->messageHandler->findMsgByIdx(dbcFile->messageHandler->getCount() - 1); QTreeWidgetItem *newMsgItem = new QTreeWidgetItem(); QString msgInfo = Utility::formatCANID(msg.ID) + " " + msg.name; if (msg.comment.count() > 0) msgInfo.append(" - ").append(msg.comment); newMsgItem->setText(0, msgInfo); newMsgItem->setIcon(0, messageIcon); newMsgItem->setData(0, Qt::UserRole, DBCItemTypes::MESG); messageToItem.insert(msgPtr, newMsgItem); itemToMessage.insert(newMsgItem, msgPtr); nodeItem->addChild(newMsgItem); ui->treeDBC->setCurrentItem(newMsgItem); dbcFile->setDirtyFlag(); } void DBCMainEditor::newSignal() { QTreeWidgetItem *msgItem = nullptr; QTreeWidgetItem *sigItem = nullptr; QTreeWidgetItem *parentItem = nullptr; msgItem = ui->treeDBC->currentItem(); parentItem = msgItem; if (!msgItem) return; //nothing selected! int typ = msgItem->data(0, Qt::UserRole).toInt(); if (typ == DBCItemTypes::NODE) return; //can't add signals to a node! if (typ == DBCItemTypes::SIG) { sigItem = msgItem; msgItem = msgItem->parent(); parentItem = msgItem; //walk up the tree to find the parent msg while (msgItem && msgItem->data(0, Qt::UserRole).toInt() != DBCItemTypes::MESG) msgItem = msgItem->parent(); if (!msgItem) return; //something bad happened. abort. } QString idString = msgItem->text(0).split(" ")[0]; int msgID = static_cast(Utility::ParseStringToNum(idString)); DBC_MESSAGE *msg = dbcFile->messageHandler->findMsgByID(msgID); if (!msg) return; //null pointers are a bummer. Do not follow them. DBC_SIGNAL sig; DBC_SIGNAL *sigPtr; if (sigItem) { QString txt = sigItem->text(0); if (txt.startsWith('(')) txt = txt.split(" ")[1]; //if it was a multiplexed signal we need to ignore that part and still grab sig name else txt = txt.split(" ")[0]; DBC_SIGNAL *oldSig = msg->sigHandler->findSignalByName(txt); if (oldSig) { sig = *oldSig; sig.name = sig.name + QString::number(randGen.bounded(100)); } else { sig.name = msgItem->text(0).split(" ")[1] + "Sig" + QString::number(randGen.bounded(500)); } } else { sig.name = msgItem->text(0).split(" ")[1] + "Sig" + QString::number(randGen.bounded(500)); } sig.parentMessage = msg; if (!sig.receiver) sig.receiver = &dbcFile->dbc_nodes[0]; //if receiver not set then set it to... something. msg->sigHandler->addSignal(sig); sigPtr = msg->sigHandler->findSignalByIdx(msg->sigHandler->getCount() - 1); QTreeWidgetItem *newSigItem = new QTreeWidgetItem(); QString sigInfo = createSignalText(&sig); newSigItem->setText(0, sigInfo); if (sig.isMultiplexed) newSigItem->setIcon(0, multiplexedSignalIcon); else if (sig.isMultiplexor) newSigItem->setIcon(0, multiplexorSignalIcon); else newSigItem->setIcon(0, signalIcon); newSigItem->setData(0, Qt::UserRole, DBCItemTypes::SIG); signalToItem.insert(sigPtr, newSigItem); itemToSignal.insert(newSigItem, sigPtr); parentItem->addChild(newSigItem); ui->treeDBC->setCurrentItem(newSigItem); dbcFile->setDirtyFlag(); } //Fills the given table with one row per message, sorted ascending by ID. Duplicate IDs are //highlighted. When showUnused is set, every free ID in the standard 11-bit range (0x000-0x7FF) //is interleaved as a greyed "free" row so an available ID is easy to pick. Rows that map to a //real message carry that message's ID in Qt::UserRole so a double-click can jump to it. void DBCMainEditor::populateUsedIDsTable(QTableWidget *table, bool showUnused) { table->clearContents(); table->setRowCount(0); if (!dbcFile) return; QVector msgs; for (int i = 0; i < dbcFile->messageHandler->getCount(); i++) msgs.append(dbcFile->messageHandler->findMsgByIdx(i)); QMap idCounts; //how often each ID appears -> duplicate detection QSet usedSet; for (DBC_MESSAGE *m : msgs) { idCounts[m->ID]++; usedSet.insert(m->ID); } struct Row { uint32_t id; DBC_MESSAGE *msg; }; //msg == nullptr marks a free ID QVector rows; for (DBC_MESSAGE *m : msgs) rows.append({m->ID, m}); if (showUnused) { for (uint32_t id = 0; id <= 0x7FF; id++) if (!usedSet.contains(id)) rows.append({id, nullptr}); } //stable sort keeps duplicate-ID messages in their original relative order std::stable_sort(rows.begin(), rows.end(), [](const Row &a, const Row &b) { return a.id < b.id; }); const QColor dupColor(0xFF, 0x88, 0x88); const QColor freeColor = table->palette().color(QPalette::Disabled, QPalette::Text); table->setRowCount(rows.count()); for (int r = 0; r < rows.count(); r++) { const Row &row = rows[r]; QTableWidgetItem *idItem = new QTableWidgetItem(Utility::formatCANID(row.id)); QTableWidgetItem *nameItem; QTableWidgetItem *nodeItem; if (row.msg) { nameItem = new QTableWidgetItem(row.msg->name); nodeItem = new QTableWidgetItem(row.msg->sender ? row.msg->sender->name : QString()); idItem->setData(Qt::UserRole, row.msg->ID); //presence of this marks the row as jumpable if (idCounts.value(row.msg->ID) > 1) { idItem->setBackground(dupColor); nameItem->setBackground(dupColor); nodeItem->setBackground(dupColor); } } else { nameItem = new QTableWidgetItem(tr("— free —")); nodeItem = new QTableWidgetItem(); idItem->setForeground(freeColor); nameItem->setForeground(freeColor); } table->setItem(r, 0, idItem); table->setItem(r, 1, nameItem); table->setItem(r, 2, nodeItem); } table->resizeColumnsToContents(); } //Select and scroll to the message with the given ID in the main tree. void DBCMainEditor::jumpToMessage(uint32_t msgID) { if (!dbcFile) return; DBC_MESSAGE *msg = dbcFile->messageHandler->findMsgByID(msgID); if (!msg || !messageToItem.contains(msg)) return; QTreeWidgetItem *item = messageToItem.value(msg); if (item->parent()) item->parent()->setExpanded(true); ui->treeDBC->setCurrentItem(item); ui->treeDBC->scrollToItem(item); } //Builds a modeless window listing every message ID in use, sorted ascending, with duplicate //IDs highlighted. If any duplicates exist a warning dialog is shown listing them. void DBCMainEditor::showUsedIDs() { if (!dbcFile) return; QDialog *dlg = new QDialog(this); dlg->setAttribute(Qt::WA_DeleteOnClose); dlg->setWindowTitle(tr("Used Message IDs")); dlg->resize(450, 550); QVBoxLayout *lay = new QVBoxLayout(dlg); QCheckBox *chkUnused = new QCheckBox(tr("Show unused IDs (0x000-0x7FF)"), dlg); lay->addWidget(chkUnused); QTableWidget *table = new QTableWidget(dlg); table->setColumnCount(3); table->setHorizontalHeaderLabels(QStringList() << tr("ID") << tr("Message") << tr("Node")); table->setEditTriggers(QAbstractItemView::NoEditTriggers); table->setSelectionBehavior(QAbstractItemView::SelectRows); table->verticalHeader()->setVisible(false); table->horizontalHeader()->setStretchLastSection(true); lay->addWidget(table); QLabel *hint = new QLabel(tr("Double-click a message to jump to it in the tree."), dlg); lay->addWidget(hint); populateUsedIDsTable(table, false); connect(chkUnused, &QCheckBox::toggled, this, [this, table](bool checked) { populateUsedIDsTable(table, checked); }); connect(table, &QTableWidget::cellDoubleClicked, this, [this, table](int row, int /*col*/) { QTableWidgetItem *idItem = table->item(row, 0); if (idItem && idItem->data(Qt::UserRole).isValid()) jumpToMessage(idItem->data(Qt::UserRole).toUInt()); }); dlg->show(); //assemble the list of duplicated IDs and warn about them on open (QMap keeps keys sorted) QMap idCounts; for (int i = 0; i < dbcFile->messageHandler->getCount(); i++) idCounts[dbcFile->messageHandler->findMsgByIdx(i)->ID]++; QStringList dupStrings; for (auto it = idCounts.begin(); it != idCounts.end(); ++it) if (it.value() > 1) dupStrings.append(Utility::formatCANID(it.key()) + " (" + QString::number(it.value()) + " messages)"); if (!dupStrings.isEmpty()) { QMessageBox::warning(dlg, tr("Duplicate IDs Detected"), tr("The following message IDs are used more than once:\n\n") + dupStrings.join("\n")); } } //gets confirmation before calling the real routines that delete things void DBCMainEditor::deleteCurrentTreeItem() { QTreeWidgetItem *currItem = ui->treeDBC->currentItem(); int typ = currItem->data(0, Qt::UserRole).toInt(); QString idString, columnText; int msgID; DBC_MESSAGE *msg; DBC_NODE *node; int numMsg = 0, numSig = 0; QMessageBox::StandardButton confirmDialog; switch (typ) { case DBCItemTypes::NODE: //deleting a node cascades deletion down to messages and signals columnText = currItem->text(0); node = dbcFile->findNodeByNameAndComment(columnText); if (!node) return; for (int x = 0; x < dbcFile->messageHandler->getCount(); x++) { if (dbcFile->messageHandler->findMsgByIdx(x)->sender == node) { numMsg++; numSig += dbcFile->messageHandler->findMsgByIdx(x)->sigHandler->getCount(); } } confirmDialog = QMessageBox::question(this, "Really?", "Are you sure you want to delete this node, its\n" + QString::number(numMsg) + " messages, and their " + QString::number(numSig) + " combined signals?", QMessageBox::Yes|QMessageBox::No); if (confirmDialog == QMessageBox::Yes) { if (itemToNode.contains(currItem)) { DBC_NODE *node = itemToNode.value(currItem); deleteNode(node); } else { qDebug() << "Could not find the node in the map. That should not happen."; } } break; case DBCItemTypes::MESG: //cascades to removing all signals too. idString = currItem->text(0).split(" ")[0]; msgID = static_cast(Utility::ParseStringToNum(idString)); msg = dbcFile->messageHandler->findMsgByID(msgID); confirmDialog = QMessageBox::question(this, "Really?", "Are you sure you want to delete\nthis message and its " + QString::number(msg->sigHandler->getCount()) + " signals?", QMessageBox::Yes|QMessageBox::No); if (confirmDialog == QMessageBox::Yes) { QTreeWidgetItem *currItem = ui->treeDBC->currentItem(); if (itemToMessage.contains(currItem)) { DBC_MESSAGE *msg = itemToMessage.value(currItem); deleteMessage(msg); } else { qDebug() << "Could not find the message in the map. That should not happen."; } } break; case DBCItemTypes::SIG: //no cascade, just this one signal. confirmDialog = QMessageBox::question(this, "Really?", "Are you sure you want to delete this signal?", QMessageBox::Yes|QMessageBox::No); if (confirmDialog == QMessageBox::Yes) { QTreeWidgetItem *currItem = ui->treeDBC->currentItem(); if (itemToSignal.contains(currItem)) { DBC_SIGNAL *sig = itemToSignal.value(currItem); deleteSignal(sig); } else { qDebug() << "Could not find the signal in the map. That should not happen."; } } break; } } //these don't ask for permission. You call it, it disappears forever. void DBCMainEditor::deleteNode(DBC_NODE *node) { qDebug() << "Going through with it you mass deleter!"; if (!nodeToItem.contains(node)) return; QTreeWidgetItem *currItem = nodeToItem[node]; //don't actually store which messages are associated to which nodes so just iterate through the messages list and whack the ones //that claim to be associated to this node. int numItems = dbcFile->messageHandler->getCount(); for (int i = numItems - 1; i > -1; i--) { DBC_MESSAGE *msg = dbcFile->messageHandler->findMsgByIdx(i); if (msg->sender == node) deleteMessage(dbcFile->messageHandler->findMsgByIdx(i)); //also, each signal has a receiver field that references the nodes. It was probably stupid to store //pointers to node structures in signals but that's how it is currently. Need to iterate over all //signals in all messages and set the receiver field to Vector__XXX if the old receiver node was this one. else //still check for signals with receiver set to this node { DBC_NODE *unset_node = dbcFile->findNodeByName("Vector__XXX"); int numSigs = msg->sigHandler->getCount(); for (int j = numSigs - 1; j > -1; j--) { DBC_SIGNAL *sig = msg->sigHandler->findSignalByIdx(j); if (sig->receiver == node) sig->receiver = unset_node; } } } nodeToItem.remove(node); itemToNode.remove(currItem); ui->treeDBC->removeItemWidget(currItem, 0); delete currItem; for (int j = 0; j < dbcFile->dbc_nodes.count(); j++) { if (dbcFile->dbc_nodes.at(j).name == node->name) { dbcFile->dbc_nodes.removeAt(j); break; } } dbcFile->setDirtyFlag(); } void DBCMainEditor::deleteMessage(DBC_MESSAGE *msg) { qDebug() << "Deleting the message and all signals. Bye bye!"; if (!messageToItem.contains(msg)) return; QTreeWidgetItem *currItem = messageToItem[msg]; int numItems = msg->sigHandler->getCount(); for (int i = numItems - 1; i > -1; i--) { deleteSignal(msg->sigHandler->findSignalByIdx(i)); } dbcFile->messageHandler->removeMessage(msg); itemToMessage.remove(currItem); messageToItem.remove(msg); ui->treeDBC->removeItemWidget(currItem, 0); delete currItem; dbcFile->setDirtyFlag(); } void DBCMainEditor::deleteSignal(DBC_SIGNAL *sig) { qDebug() << "Signal about to vanish."; if (!signalToItem.contains(sig)) return; QTreeWidgetItem *currItem = signalToItem[sig]; sig->parentMessage->sigHandler->removeSignal(sig); itemToSignal.remove(currItem); signalToItem.remove(sig); ui->treeDBC->removeItemWidget(currItem, 0); //delete currItem; //already removed by above remove call dbcFile->setDirtyFlag(); }