text: #include "dbcsignaleditor.h"
  #include "ui_dbcsignaleditor.h"
  #include <QDateTime>
  #include <QDebug>
  #include <QMenu>
  #include <QSettings>
  #include <qevent.h>
  #include "helpwindow.h"
  
  DBCSignalEditor::DBCSignalEditor(QWidget *parent) :
      QDialog(parent),
      ui(new Ui::DBCSignalEditor)
  {
      ui->setupUi(this);
  
      readSettings();
  
      qsrand(QDateTime::currentMSecsSinceEpoch());
  
      dbcHandler = DBCHandler::getReference();
      dbcMessage = nullptr;
      currentSignal = nullptr;
  
      QStringList headers2;
      headers2 << "Value" << "Text";
      ui->valuesTable->setColumnCount(2);
      ui->valuesTable->setColumnWidth(0, 200);
      ui->valuesTable->setColumnWidth(1, 440);
      ui->valuesTable->setHorizontalHeaderLabels(headers2);
      ui->valuesTable->horizontalHeader()->setStretchLastSection(true);
  
      ui->comboType->addItem("UNSIGNED INTEGER");
      ui->comboType->addItem("SIGNED INTEGER");
      ui->comboType->addItem("SINGLE PRECISION");
      ui->comboType->addItem("DOUBLE PRECISION");
      ui->comboType->addItem("STRING");
  
      connect(ui->signalsList, SIGNAL(currentRowChanged(int)), this, SLOT(clickSignalList(int)));
      connect(ui->bitfield, SIGNAL(gridClicked(int,int)), this, SLOT(bitfieldClicked(int,int)));
      connect(ui->signalsList, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(onCustomMenuSignals(QPoint)));
      ui->signalsList->setContextMenuPolicy(Qt::CustomContextMenu);
      connect(ui->valuesTable, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(onCustomMenuValues(QPoint)));
      ui->valuesTable->setContextMenuPolicy(Qt::CustomContextMenu);
      connect(ui->valuesTable, SIGNAL(cellChanged(int,int)), this, SLOT(onValuesCellChanged(int,int)));
  
      //now with 100% more lambda expressions just to make it interesting (and shorter, and easier...)
      connect(ui->cbIntelFormat, &QCheckBox::toggled,
              [=]()
              {
                  if (currentSignal == nullptr) return;
                  currentSignal->intelByteOrder = ui->cbIntelFormat->isChecked();
                  if (currentSignal->valType == SP_FLOAT || currentSignal->valType == DP_FLOAT)
                      currentSignal->intelByteOrder = false;
                  fillSignalForm(currentSignal);
              });
  
      connect(ui->comboReceiver, &QComboBox::currentTextChanged,
              [=]()
              {
                  if (currentSignal == nullptr) return;
                  currentSignal->receiver = dbcFile->findNodeByName(ui->comboReceiver->currentText());
              });
      connect(ui->comboType, &QComboBox::currentTextChanged,
              [=]()
              {
                  if (currentSignal == nullptr) return;
                  switch (ui->comboType->currentIndex())
                  {
                  case 0:
                      currentSignal->valType = UNSIGNED_INT;
                      break;
                  case 1:
                      currentSignal->valType = SIGNED_INT;
                      break;
                  case 2:
                      currentSignal->valType = SP_FLOAT;
                      currentSignal->intelByteOrder = false;
                      if (currentSignal->startBit > 39) currentSignal->startBit = 39;
                      currentSignal->signalSize = 32;
                      break;
                  case 3:
                      currentSignal->valType = DP_FLOAT;
                      currentSignal->intelByteOrder = false;
                      currentSignal->startBit = 7; //has to be!
                      currentSignal->signalSize = 64;
                      break;
                  case 4:
                      currentSignal->valType = STRING;
                      break;                    
                  }
                  fillSignalForm(currentSignal);
              });
      connect(ui->txtBias, &QLineEdit::editingFinished,
              [=]()
              {
                  if (currentSignal == nullptr) return;
                  double temp;
                  bool result;
                  temp = ui->txtBias->text().toDouble(&result);
                  if (result) currentSignal->bias = temp;
              });
  
      connect(ui->txtMaxVal, &QLineEdit::editingFinished,
              [=]()
              {
                  if (currentSignal == nullptr) return;
                  double temp;
                  bool result;
                  temp = ui->txtMaxVal->text().toDouble(&result);
                  if (result) currentSignal->max = temp;
              });
  
      connect(ui->txtMinVal, &QLineEdit::editingFinished,
              [=]()
              {
                  if (currentSignal == nullptr) return;
                  double temp;
                  bool result;
                  temp = ui->txtMinVal->text().toDouble(&result);
                  if (result) currentSignal->min = temp;
              });
      connect(ui->txtScale, &QLineEdit::editingFinished,
              [=]()
              {
                  if (currentSignal == nullptr) return;
                  double temp;
                  bool result;
                  temp = ui->txtScale->text().toDouble(&result);
                  if (result) currentSignal->factor = temp;
              });
      connect(ui->txtComment, &QLineEdit::editingFinished,
              [=]()
              {
                  if (currentSignal == nullptr) return;
                  currentSignal->comment = ui->txtComment->text().simplified().replace(' ', '_');
              });
  
      connect(ui->txtUnitName, &QLineEdit::editingFinished,
              [=]()
              {
                  if (currentSignal == nullptr) return;
                  currentSignal->unitName = ui->txtUnitName->text().simplified().replace(' ', '_');
              });
      connect(ui->txtBitLength, &QLineEdit::textChanged,
              [=]()
              {
                  if (currentSignal == nullptr) return;
                  int temp;
                  temp = Utility::ParseStringToNum(ui->txtBitLength->text());
                  if (temp < 0) return;
                  if (temp > 63) return;
                  if (currentSignal->valType != SP_FLOAT && currentSignal->valType != DP_FLOAT)
                      currentSignal->signalSize = temp;
                  fillSignalForm(currentSignal);
              });
      connect(ui->txtName, &QLineEdit::editingFinished,
              [=]()
              {
                  if (currentSignal == nullptr) return;
                  QString tempNameStr = ui->txtName->text().simplified().replace(' ', '_');
                  if (tempNameStr.length() > 0) currentSignal->name = tempNameStr;
                  //need to update the list too.
                  ui->signalsList->currentItem()->setText(currentSignal->name);
              });
  
      connect(ui->txtMultiplexValue, &QLineEdit::editingFinished,
              [=]()
              {
                  if (currentSignal == nullptr) return;
                  int temp;
                  temp = Utility::ParseStringToNum(ui->txtMultiplexValue->text());
                  //TODO: could look up the multiplexor and ensure that the value is within a range that the multiplexor could return
                  currentSignal->multiplexValue = temp;
              });
      connect(ui->rbMultiplexed, &QRadioButton::toggled,
              [=](bool state)
              {
                  if (state && currentSignal) //signal is now set as a multiplexed signal
                  {
                      currentSignal->isMultiplexed = true;
                      currentSignal->isMultiplexor = false;
                      //if the set multiplexor for the message was this signal then clear it
                      if (dbcMessage->multiplexorSignal == currentSignal) dbcMessage->multiplexorSignal = nullptr;
                  }
              });
  
      connect(ui->rbMultiplexor, &QRadioButton::toggled,
              [=](bool state)
              {
                  if (state && currentSignal) //signal is now set as a multiplexed signal
                  {
                      //don't allow this signal to be a multiplexor if there is already one for this message.
                      //if (dbcMessage->multiplexorSignal != currentSignal && dbcMessage->multiplexorSignal != nullptr) return; //I spoke too soon above...
                      currentSignal->isMultiplexed = false;
                      currentSignal->isMultiplexor = true;
                      //we just set that this is the multiplexor so update the message to show that as well.
                      dbcMessage->multiplexorSignal = currentSignal;
                  }
              });
  
      connect(ui->rbNotMulti, &QRadioButton::toggled,
              [=](bool state)
              {
                  if (state && currentSignal) //signal is now set as a multiplexed signal
                  {
                      currentSignal->isMultiplexed = false;
                      currentSignal->isMultiplexor = false;
                      if (dbcMessage->multiplexorSignal == currentSignal) dbcMessage->multiplexorSignal = nullptr;
                  }
              });
  
      installEventFilter(this);
  }
  
  DBCSignalEditor::~DBCSignalEditor()
  {
      removeEventFilter(this);
      delete ui;
  }
  
  void DBCSignalEditor::closeEvent(QCloseEvent *event)
  {
      Q_UNUSED(event);
      writeSettings();
  }
  
  bool DBCSignalEditor::eventFilter(QObject *obj, QEvent *event)
  {
      if (event->type() == QEvent::KeyRelease) {
          QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event);
          switch (keyEvent->key())
          {
          case Qt::Key_F1:
              HelpWindow::getRef()->showHelp("signaleditor.html");
              break;
          case Qt::Key_F2:
              cloneSignal();
              break;
          }
          return true;
      } else {
          // standard event processing
          return QObject::eventFilter(obj, event);
      }
      return false;
  }
  
  void DBCSignalEditor::setFileIdx(int idx)
  {
      if (idx < 0 || idx > dbcHandler->getFileCount() - 1) return;
      dbcFile = dbcHandler->getFileByIdx(idx);
  
      for (int x = 0; x < dbcFile->dbc_nodes.count(); x++)
      {
          ui->comboReceiver->addItem(dbcFile->dbc_nodes[x].name);
      }
  }
  
  void DBCSignalEditor::readSettings()
  {
      QSettings settings;
      if (settings.value("Main/SaveRestorePositions", false).toBool())
      {
          resize(settings.value("DBCSignalEditor/WindowSize", QSize(1000, 600)).toSize());
          move(settings.value("DBCSignalEditor/WindowPos", QPoint(100, 100)).toPoint());
      }
  }
  
  void DBCSignalEditor::writeSettings()
  {
      QSettings settings;
  
      if (settings.value("Main/SaveRestorePositions", false).toBool())
      {
          settings.setValue("DBCSignalEditor/WindowSize", size());
          settings.setValue("DBCSignalEditor/WindowPos", pos());
      }
  }
  
  
  void DBCSignalEditor::setMessageRef(DBC_MESSAGE *msg)
  {
      dbcMessage = msg;
  }
  
  void DBCSignalEditor::showEvent(QShowEvent* event)
  {
      QDialog::showEvent(event);
  
      currentSignal = nullptr;
      refreshSignalsList();
  }
  
  void DBCSignalEditor::onValuesCellChanged(int row,int col)
  {
      if (inhibitCellChanged) return;
  
      if (row == ui->valuesTable->rowCount() - 1)
      {
          DBC_VAL_ENUM_ENTRY newVal;
          newVal.value = 0;
          newVal.descript = "No Description";
          currentSignal->valList.append(newVal);
          qDebug() << "Created new entry in value list";
  
          //QTableWidgetItem *widgetVal = new QTableWidgetItem(QString::number(newVal.value));
          //ui->valuesTable->setItem(row, 0, widgetVal);
          //QTableWidgetItem *widgetDesc = new QTableWidgetItem(newVal.descript);
          //ui->valuesTable->setItem(row, 1, widgetDesc);
  
          //add the blank at the end again
          ui->valuesTable->insertRow(ui->valuesTable->rowCount());
      }
  
      if (col == 0)
      {
          currentSignal->valList[row].value = Utility::ParseStringToNum(ui->valuesTable->item(row, col)->text());
      }
      else if (col == 1)
      {
          currentSignal->valList[row].descript = ui->valuesTable->item(row, col)->text().simplified().replace(' ', '_');
      }
  }
  
  void DBCSignalEditor::onCustomMenuSignals(QPoint point)
  {
      QMenu *menu = new QMenu(this);
      menu->setAttribute(Qt::WA_DeleteOnClose);
  
      menu->addAction(tr("Add a new signal"), this, SLOT(addNewSignal()));
      if (ui->signalsList->currentRow() != -1)
      {
          menu->addAction(tr("Clone currently selected signal"), this, SLOT(cloneSignal()));
          menu->addAction(tr("Delete currently selected signal"), this, SLOT(deleteCurrentSignal()));
      }
  
      menu->popup(ui->signalsList->mapToGlobal(point));
  }
  
  void DBCSignalEditor::onCustomMenuValues(QPoint point)
  {
      QMenu *menu = new QMenu(this);
      menu->setAttribute(Qt::WA_DeleteOnClose);
  
      menu->addAction(tr("Delete currently selected value"), this, SLOT(deleteCurrentValue()));
  
      menu->popup(ui->valuesTable->mapToGlobal(point));
  }
  
  void DBCSignalEditor::addNewSignal()
  {
      int num = qrand() % 70000;
      QString newName = "SIGNAL" + QString::number(num);
      DBC_SIGNAL newSig;
      newSig.name = newName;
      newSig.bias = 0.0;
      newSig.factor = 1.0;
      newSig.intelByteOrder = true;
      newSig.max = 0.0;
      newSig.min = 0.0;
      newSig.receiver = dbcFile->findNodeByIdx(0);
      newSig.signalSize = 1;
      newSig.startBit = 0;
      newSig.valType = UNSIGNED_INT;
      newSig.isMultiplexed = false;
      newSig.isMultiplexor = false;
      newSig.multiplexValue = 0;
      newSig.parentMessage = dbcMessage;
  
      dbcMessage->sigHandler->addSignal(newSig);
  
      /* add item at the end of the list and select it */
      /* this will call clickSignalList */
      ui->signalsList->addItem(newName);
      ui->signalsList->setCurrentRow(ui->signalsList->count()-1);
  }
  
  void DBCSignalEditor::cloneSignal()
  {
      int num = qrand() % 100;
  
      int idx = ui->signalsList->currentRow();
      if (idx < 0) return;
  
      DBC_SIGNAL *oldSig = dbcMessage->sigHandler->findSignalByIdx(idx);
      if (!oldSig) return;
  
      QString newName = oldSig->name + QString::number(num);
      DBC_SIGNAL newSig;
      newSig.name = newName;
      newSig.bias = oldSig->bias;
      newSig.factor = oldSig->factor;
      newSig.intelByteOrder = oldSig->intelByteOrder;
      newSig.max = oldSig->max;
      newSig.min = oldSig->min;
      newSig.receiver = oldSig->receiver;
      newSig.signalSize = oldSig->signalSize;
      newSig.startBit = oldSig->startBit;
      newSig.valType = oldSig->valType;
      newSig.valList.append(oldSig->valList);
      newSig.isMultiplexed = oldSig->isMultiplexed;
      newSig.isMultiplexor = oldSig->isMultiplexor; //maybe should force this false since you can't have two or more!
      newSig.multiplexValue = oldSig->multiplexValue;
      newSig.parentMessage = dbcMessage;
  
      dbcMessage->sigHandler->addSignal(newSig);
  
      /* add item at the end of the list */
      ui->signalsList->addItem(newName);
  
      //unlike adding a signal we don't want to select the new signal here.
      //ui->signalsList->setCurrentRow(ui->signalsList->count()-1);
  }
  
  void DBCSignalEditor::deleteCurrentSignal()
  {
      int currIdx = ui->signalsList->currentRow();
  
      //if(currIdx==ui->signalsList->count()-1)
          //return;
  
      if (currIdx > -1)
      {
          delete(ui->signalsList->item(currIdx));
          dbcMessage->sigHandler->removeSignal(currIdx);
          currentSignal = nullptr;
      }
  }
  
  void DBCSignalEditor::deleteCurrentValue()
  {
      int currIdx = ui->valuesTable->currentRow();
      if (currIdx > -1)
      {
          ui->valuesTable->removeRow(currIdx);
          currentSignal->valList.removeAt(currIdx);
      }
  }
  
  void DBCSignalEditor::refreshSignalsList()
  {
      ui->signalsList->clear();
  
  
      for (int x = 0; x < dbcMessage->sigHandler->getCount(); x++)
      {
          DBC_SIGNAL *sig = dbcMessage->sigHandler->findSignalByIdx(x);
          ui->signalsList->addItem(sig->name);
      }
  
      if( ui->signalsList->count()>0 ) {
          /* click first element */
          ui->signalsList->setCurrentRow(0);
      }
      else
          clickSignalList(-1);
  }
  
  /* fillSignalForm also handles group "enabled" state */
  /* WARNING: fillSignalForm can be called recursively since it is in the listener of cbIntelFormat */
  void DBCSignalEditor::fillSignalForm(DBC_SIGNAL *sig)
  {
      unsigned char bitpattern[8];
  
      if (sig == nullptr) {
          ui->groupBox->setEnabled(false);
          ui->txtName->setText("");
          ui->txtBias->setText("");
          ui->txtBitLength->setText("");
          ui->txtComment->setText("");
          ui->txtMaxVal->setText("");
          ui->txtMinVal->setText("");
          ui->txtScale->setText("");
          ui->txtUnitName->setText("");
          ui->txtMultiplexValue->setText("");
          ui->rbMultiplexed->setChecked(false);
          ui->rbMultiplexor->setChecked(false);
          ui->rbNotMulti->setChecked(true);
          memset(bitpattern, 0, 8); //clear it out
          ui->bitfield->setReference(bitpattern, false);
          ui->bitfield->updateData(bitpattern, true);
          ui->comboReceiver->setCurrentIndex(0);
          ui->comboType->setCurrentIndex(0);
          return;
      }
  
      /* we have a signal */
      ui->groupBox->setEnabled(true);
  
      generateUsedBits();
      ui->txtName->setText(sig->name);
      ui->txtBias->setText(QString::number(sig->bias));
      ui->txtBitLength->setText(QString::number(sig->signalSize));
      ui->txtMultiplexValue->setText(QString::number(sig->multiplexValue));
      ui->txtComment->setText(sig->comment);
      ui->txtMaxVal->setText(QString::number(sig->max));
      ui->txtMinVal->setText(QString::number(sig->min));
      ui->txtScale->setText(QString::number(sig->factor));
      ui->txtUnitName->setText(sig->unitName);
      ui->rbMultiplexed->setChecked(sig->isMultiplexed);
      ui->rbMultiplexor->setChecked(sig->isMultiplexor);
      ui->rbNotMulti->setChecked( !(sig->isMultiplexor | sig->isMultiplexed) );
      qDebug() << sig->isMultiplexor << "*" << sig->isMultiplexed;
  
      memset(bitpattern, 0, 8); //clear it out first.
  
      int startBit, endBit;
  
      startBit = sig->startBit;
  
      bitpattern[startBit / 8] |= 1 << (startBit % 8); //make the start bit a different color to set it apart
      ui->bitfield->setReference(bitpattern, false);
  
      if (sig->intelByteOrder)
      {
          endBit = startBit + sig->signalSize - 1;
          if (startBit < 0) startBit = 0;
          if (endBit > 63) endBit = 63;
          for (int y = startBit; y <= endBit; y++)
          {
              int byt = y / 8;
              bitpattern[byt] |= 1 << (y % 8);
          }
      }
      else //big endian / motorola format
      {
          //much more irritating than the intel version...
          int size = sig->signalSize;
          while (size > 0)
          {
              int byt = startBit / 8;
              bitpattern[byt] |= 1 << (startBit % 8);
              size--;
              if ((startBit % 8) == 0) startBit += 15;            
              else startBit--;
              if (startBit > 63) startBit = 63;
          }
      }
  
      ui->bitfield->updateData(bitpattern, true);
      ui->cbIntelFormat->setChecked(sig->intelByteOrder);
  
      switch (sig->valType)
      {
      case UNSIGNED_INT:
          ui->comboType->setCurrentIndex(0);
          break;
      case SIGNED_INT:
          ui->comboType->setCurrentIndex(1);
          break;
      case SP_FLOAT:
          ui->comboType->setCurrentIndex(2);
          break;
      case DP_FLOAT:
          ui->comboType->setCurrentIndex(3);
          break;
      case STRING:
          ui->comboType->setCurrentIndex(4);
          break;
      }
  
      for (int i = 0; i < ui->comboReceiver->count(); i++)
      {
          if (ui->comboReceiver->itemText(i) == sig->receiver->name)
          {
              ui->comboReceiver->setCurrentIndex(i);
              break;
          }
      }
  }
  
  /* fillValueTable also handles "enabled" state */
  void DBCSignalEditor::fillValueTable(DBC_SIGNAL *sig)
  {
      int rowIdx;
  
      inhibitCellChanged = true;
  
      ui->valuesTable->clearContents();
      ui->valuesTable->setRowCount(0);
  
      if (sig == nullptr) {
          ui->valuesTable->setEnabled(false);
          inhibitCellChanged = false;
          return;
      }
  
      ui->valuesTable->setEnabled(true);
  
      for (int i = 0; i < sig->valList.count(); i++)
      {
          QTableWidgetItem *val = new QTableWidgetItem(Utility::formatNumber(sig->valList[i].value));
          QTableWidgetItem *desc = new QTableWidgetItem(sig->valList[i].descript);
          rowIdx = ui->valuesTable->rowCount();
          ui->valuesTable->insertRow(rowIdx);
          ui->valuesTable->setItem(rowIdx, 0, val);
          ui->valuesTable->setItem(rowIdx, 1, desc);
      }
  
      //Add a blank row at the end to allow for adding records easily
      ui->valuesTable->insertRow(ui->valuesTable->rowCount());
  
      inhibitCellChanged = false;
  }
  
  void DBCSignalEditor::clickSignalList(int row)
  {
      //qDebug() << ui->signalsList->item(row)->text();
  
      DBC_SIGNAL *thisSig = (row<0) ? nullptr : dbcMessage->sigHandler->findSignalByName(ui->signalsList->item(row)->text());
      currentSignal = thisSig;
      fillSignalForm(thisSig);
      fillValueTable(thisSig);
  }
  
  void DBCSignalEditor::bitfieldClicked(int x, int y)
  {
      int bit = (7 - x) + (y * 8);
      if (currentSignal == nullptr) return;
      currentSignal->startBit = bit;
      if (currentSignal->valType == SP_FLOAT)
      {
          if (currentSignal->startBit > 31) currentSignal->startBit = 39;
      }
  
      if (currentSignal->valType == DP_FLOAT)
          currentSignal->startBit = 7;
      fillSignalForm(currentSignal);
  }
  
  void DBCSignalEditor::generateUsedBits()
  {
      uint8_t usedBits[8] = {0,0,0,0,0,0,0,0};
      int startBit, endBit;
  
      for (int x = 0; x < dbcMessage->sigHandler->getCount(); x++)
      {
          DBC_SIGNAL *sig = dbcMessage->sigHandler->findSignalByIdx(x);
  
          startBit = sig->startBit;
  
          if (sig->intelByteOrder)
          {
              endBit = startBit + sig->signalSize - 1;
              if (startBit < 0) startBit = 0;
              if (endBit > 63) endBit = 63;
              for (int y = startBit; y <= endBit; y++)
              {
                  int byt = y / 8;
                  usedBits[byt] |= 1 << (y % 8);
              }
          }
          else //big endian / motorola format
          {
              //much more irritating than the intel version...
              int size = sig->signalSize;
              while (size > 0)
              {
                  int byt = startBit / 8;
                  usedBits[byt] |= 1 << (startBit % 8);
                  size--;
                  if ((startBit % 8) == 0) startBit += 15;
                  else startBit--;
                  if (startBit > 63) startBit = 63;
              }
          }
      }
      ui->bitfield->setUsed(usedBits, false);
  }
  