82a6d74d创建于 2025年4月30日历史提交
/***************************************************************************
                              simmessage.cpp
                             ----------------
    begin                : Sat Sep 6 2003
    copyright            : (C) 2003 by Michael Margraf
    email                : michael.margraf@alumni.tu-berlin.de
 ***************************************************************************/

/***************************************************************************
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 ***************************************************************************/

/*!
 * \file simmessage.cpp
 * \brief Simulation dialog implementation
 *
 */

#include <QLabel>
#include <QGroupBox>
#include <QTimer>
#include <QPushButton>
#include <QPlainTextEdit>
#include <QDateTime>
#include <QRegularExpression>
#include <QRegularExpressionValidator>
#include <QTextStream>
#include <QVBoxLayout>
#include <QHBoxLayout>
#include <QProcess>
#include <QProgressBar>
#include <QDebug>
#include <QMessageBox>

#include <filesystem>

#include "simmessage.h"
#include "main.h"
#include "module.h"
#include "qucs.h"
#include "textdoc.h"
#include "schematic.h"
#include "components/opt_sim.h"
#include "components/vhdlfile.h"
#include "misc.h"

#if defined(_WIN32) || defined(__MINGW32__)
#define executableSuffix ".exe"
#else
#define executableSuffix ""
#endif

/*!
 * \brief Create a simulation messages dialog.
 *
 *  Create a new dialog to show the simulation steps progress and the
 *  simulator output messages
 */
SimMessage::SimMessage(QWidget *w, QWidget *parent)
      : QDialog(parent)
{
  setWindowTitle(tr("Qucs Simulation Messages"));
  QucsDoc *Doc;
  DocWidget = w;
  if(QucsApp::isTextDocument(DocWidget))
    Doc = (QucsDoc*) ((TextDoc*)DocWidget);
  else
    Doc = (QucsDoc*) ((Schematic*)DocWidget);

  DocName = Doc->getDocName();
  DataDisplay = Doc->getDataDisplay();
  Script = Doc->getScript();
  QFileInfo Info(DocName);
  DataSet = QDir::toNativeSeparators(Info.path()) +
    QDir::separator() + Doc->getDataSet();
  showBias = Doc->getShowBias();     // save some settings as the document...
  SimOpenDpl = Doc->getSimOpenDpl(); // ...could be closed during the simulation.
  SimRunScript = Doc->getSimRunScript();

  all = new QVBoxLayout(this);
  all->setSpacing(5);
  all->setContentsMargins(5,5,5,5);
  QGroupBox *Group1 = new QGroupBox(tr("Progress:"));
  all->addWidget(Group1);
  QVBoxLayout *vbox1 = new QVBoxLayout();
  Group1->setLayout(vbox1);

  ProgText = new QPlainTextEdit();
  vbox1->addWidget(ProgText);
  ProgText->setReadOnly(true);
  //ProgText->setWordWrapMode(QTextOption::NoWrap);
  ProgText->setMinimumSize(400,80);
  wasLF = false;
  simKilled = false;

  QGroupBox *HGroup = new QGroupBox();
  QHBoxLayout *hbox = new QHBoxLayout();
  //HGroup->setInsideMargin(5);
  //HGroup->setInsideSpacing(5);
  all->addWidget(HGroup);
  QLabel *progr = new QLabel(tr("Progress:"));
  hbox->addWidget(progr);
  SimProgress = new QProgressBar();
  hbox->addWidget(SimProgress);
//  SimProgress->setPercentageVisible(false);
  HGroup->setLayout(hbox);

  QGroupBox *Group2 = new QGroupBox(tr("Errors and Warnings:"));
  all->addWidget(Group2);
  QVBoxLayout *vbox2 = new QVBoxLayout();

  ErrText = new QPlainTextEdit();
  vbox2->addWidget(ErrText);
  ErrText->setReadOnly(true);
  ErrText->setWordWrapMode(QTextOption::NoWrap);
  ErrText->setMinimumSize(400,80);
  Group2->setLayout(vbox2);

  QHBoxLayout *Butts = new QHBoxLayout();
  all->addLayout(Butts);

  Display = new QPushButton(tr("Goto display page"));
  Butts->addWidget(Display);
  Display->setDisabled(true);
  connect(Display,SIGNAL(clicked()),SLOT(slotDisplayButton()));

  Abort = new QPushButton(tr("Abort simulation"));
  Butts->addWidget(Abort);
  // Abort will close the window, which in turn will abort the simulation
  connect(Abort,SIGNAL(clicked()),SLOT(reject()));
  connect(this,SIGNAL(rejected()),SLOT(AbortSim()));
}

/*!
 * \brief Delete the simulation messages dialog.
 *
 *  Delete the simulation messages dialog and kill the simulation process,
 *  if still running.
 */
SimMessage::~SimMessage()
{
  if(SimProcess.state()==QProcess::Running)  SimProcess.kill();
  delete all;
}

// ------------------------------------------------------------------------
bool SimMessage::startProcess()
{
  Abort->setText(tr("Abort simulation"));
  Display->setDisabled(true);

  ProgText->clear();
  ErrText->clear();

  QString txt = tr("Starting new simulation on %1 at %2").
    arg(QDate::currentDate().toString("ddd dd. MMM yyyy")).
    arg(QTime::currentTime().toString("hh:mm:ss:zzz"));
  ProgText->appendPlainText(txt + "\n");

  SimProcess.blockSignals(false);
 /* On Qt4 it shows as running even before we .start it. FIXME*/
  if(SimProcess.state()==QProcess::Running ||SimProcess.state()==QProcess::Starting) {
    qDebug() << "running!";
    ErrText->appendPlainText(tr("ERROR: Simulator is still running!"));
    FinishSimulation(-1);
    return false;
  }

  Collect.clear();  // clear list for NodeSets, SPICE components etc.
  ProgText->appendPlainText(tr("creating netlist... "));
  NetlistFile.setFileName(QucsSettings.tempFilesDir.filePath("netlist.txt"));
   if(!NetlistFile.open(QIODevice::WriteOnly)) {
    ErrText->appendPlainText(tr("ERROR: Cannot write netlist file!"));
    FinishSimulation(-1);
    return false;
  }

  Stream.setDevice(&NetlistFile);

  if(!QucsApp::isTextDocument(DocWidget)) {
    SimPorts =
       ((Schematic*)DocWidget)->prepareNetlist(Stream, Collect, ErrText);
    if(SimPorts < -5) {
      NetlistFile.close();
      ErrText->appendPlainText(tr("ERROR: Cannot simulate a text file!"));
      FinishSimulation(-1);
      return false;
    }
  }
  Collect.append("*");   // mark the end


  disconnect(&SimProcess, 0, 0, 0);
  connect(&SimProcess, SIGNAL(readyReadStandardError()), SLOT(slotDisplayErr()));
  connect(&SimProcess, SIGNAL(readyReadStandardOutput()),
                       SLOT(slotReadSpiceNetlist()));
  connect(&SimProcess, SIGNAL(finished(int)),
                       SLOT(slotFinishSpiceNetlist(int)));

  nextSPICE();
  return true;
  // Since now, the Doc pointer may be obsolete, as the user could have
  // closed the schematic !!!
}

/*!
 * \brief Converts a spice netlist into Qucs format and outputs it.
 */
void SimMessage::nextSPICE()
{
  QString Line;
  for(;;) {  // search for next SPICE component
    Line = *(Collect.begin());
    Collect.erase(Collect.begin());
    if(Line == "*") {  // worked on all components ?
      startSimulator(); // <<<<<================== go on ===
      return;
    }
// FIXME #warning SPICE section below not being covered?
    qDebug() << "going thru SPICE branch on simmmessage.cpp";
    if(Line.left(5) == "SPICE") {
      if(Line.at(5) != 'o') insertSim = true;
      else insertSim = false;
      break;
    }
    Collect.append(Line);
  }


  QString FileName = Line.section('"', 1,1);
  Line = Line.section('"', 2);  // port nodes
  if(Line.isEmpty())  makeSubcircuit = false;
  else  makeSubcircuit = true;

  QString prog;
  QStringList com;
  prog = QucsSettings.Qucsconv;
  if(makeSubcircuit)
    com << "-g" << "_ref";
  com << "-if" << "spice" << "-of" << "qucs";

  QFile SpiceFile;
  if(FileName.indexOf(QDir::separator()) < 0)  // add path ?
    SpiceFile.setFileName(QucsSettings.QucsWorkDir.path() + QDir::separator() + FileName);
  else
    SpiceFile.setFileName(FileName);
  if(!SpiceFile.open(QIODevice::ReadOnly)) {
    ErrText->appendPlainText(tr("ERROR: Cannot open SPICE file \"%1\".").arg(FileName));
    FinishSimulation(-1);
    return;
  }

  if(makeSubcircuit) {
    Stream << "\n.Def:" << misc::properName(FileName) << " ";

    Line.replace(',', ' ');
    Stream << Line;
    if(!Line.isEmpty()) Stream << " _ref";
  }
  Stream << "\n";

  ProgressText = "";

  qDebug() << "start QucsConv" << prog << com.join(" ");
  SimProcess.start(prog, com);

  if(SimProcess.state() != QProcess::Running) {
    ErrText->appendPlainText(tr("SIM ERROR: Cannot start QucsConv!"));
    FinishSimulation(-1);
    return;
  }

  QByteArray SpiceContent = SpiceFile.readAll();
  SpiceFile.close();
  QString command(SpiceContent); //to convert byte array to string
  SimProcess.setStandardInputFile(command);  //? FIXME works?
  qDebug() << command;
  connect(&SimProcess, SIGNAL(wroteToStdin()), SLOT(slotCloseStdin()));
}

// ------------------------------------------------------------------------
void SimMessage::slotCloseStdin()
{
  //SimProcess.closeStdin(); //? channel not available in Qt4?
  disconnect(&SimProcess, SIGNAL(wroteToStdin()), 0, 0);
}

// ------------------------------------------------------------------------
void SimMessage::slotReadSpiceNetlist()
{
  int i;
  QString s;
  ProgressText += QString(SimProcess.readAllStandardOutput());

  while((i = ProgressText.indexOf('\n')) >= 0) {

    s = ProgressText.left(i);
    ProgressText.remove(0, i+1);


    s = s.trimmed();
    if(s.isEmpty()) continue;
    if(s.at(0) == '#') continue;
    if(s.at(0) == '.') if(s.left(5) != ".Def:") { // insert simulations later
      if(insertSim) Collect.append(s);
      continue;
    }
    Stream << "  " << s << '\n';
  }
}

// ------------------------------------------------------------------------
void SimMessage::slotFinishSpiceNetlist(int status )
{
  Q_UNUSED(status);

  if(makeSubcircuit)
    Stream << ".Def:End\n\n";

  nextSPICE();
}

// ------------------------------------------------------------------------
#if defined(_WIN32) || defined(__MINGW32__)
#include <windows.h>
static QString pathName(QString longpath) {
  const char * lpath = QDir::toNativeSeparators(longpath).toLatin1().data();
  char spath[2048];
  int len = GetShortPathNameA(lpath,spath,sizeof(spath)-1);
  spath[len] = '\0';
  return QString(spath);
}
#else
static QString pathName(QString longpath) {
  return longpath;
}
#endif


/*!
 * \brief SimMessage::startSimulator simulates the document in view.
 */
void SimMessage::startSimulator()
{
  // Using the Doc pointer here is risky as the user may have closed
  // the schematic, but converting the SPICE netlists is (hopefully)
  // faster than the user (I have no other idea).

  QString SimTime;
  QStringList Arguments;
  QString SimPath = QDir::toNativeSeparators(QucsSettings.tempFilesDir.absolutePath());
#if defined(_WIN32) || defined(__MINGW32__)
  QString QucsDigiLib = "qucs_mkdigilib.bat";
  QString QucsDigi = "qucs_run_hdl.bat";
  QString QucsVeri = "qucs_run_verilog.bat";
#else
  QString QucsDigiLib = "qucs_mkdigilib";
  QString QucsDigi = "qucs_run_hdl";
  QString QucsVeri = "qucs_run_verilog";
#endif
  SimOpt = NULL;
  bool isVerilog = false;
  QProcessEnvironment env = QProcessEnvironment::systemEnvironment();

  // Simulate text window.
  if(QucsApp::isTextDocument(DocWidget)) {

    TextDoc * Doc = (TextDoc*)DocWidget;

    // Take VHDL file in memory as it could contain unsaved changes.
    Stream << Doc->toPlainText();
    NetlistFile.close();
    ProgText->insertPlainText(tr("done.\n"));  // of "creating netlist...

    // Simulation.
    if (Doc->simulation) {
      SimTime = Doc->getSimTime();
      QString libs = Doc->Libraries.toLower();
      /// \todo \bug error: unrecognized command line option '-Wl'
#if defined(_WIN32) || defined(__MINGW32__)
      if(libs.isEmpty()) {
        libs = "";
      }
      else {
        libs.replace(" ",",-l");
        libs = "-Wl,-l" + libs;
      }
#else
      if(libs.isEmpty()) {
        libs = "-c";
      }
      else {
        libs.replace(" ",",-l");
        libs = "-c,-l" + libs;
      }
#endif
      // The following code runs the the qucs_run_hdl[.bat] script which in turn
      // runs GHDL (three passes with -a -e and -r commands.). Note GHDL expects the
      // time without spaces, so strip spaces from SimTime.
      Program = pathName(QucsSettings.BinDir + QucsDigi);
      Arguments  << QucsSettings.tempFilesDir.filePath("netlist.txt")
                 << DataSet << SimTime.remove(" ") << pathName(SimPath)
                 << pathName(QucsSettings.BinDir) << libs;
    }
    // Module.
    else {
      QString text = Doc->toPlainText();
      VHDL_File_Info VInfo (text);
      QString entity = VInfo.EntityName.toLower();
      QString lib = Doc->Library.toLower();
      if (lib.isEmpty()) lib = "work";
      QString dir = QDir::toNativeSeparators(QucsSettings.tempFilesDir.absolutePath());
      QDir vhdlDir(dir);
      if(!vhdlDir.exists("vhdl"))
        if(!vhdlDir.mkdir("vhdl")) {
          ErrText->appendPlainText(tr("ERROR: Cannot create VHDL directory \"%1\"!")
            .arg(vhdlDir.path()+"/vhdl"));
          return;
      }
      vhdlDir.setPath(vhdlDir.path()+"/vhdl");
      if(!vhdlDir.exists(lib))
        if(!vhdlDir.mkdir(lib)) {
          ErrText->appendPlainText(tr("ERROR: Cannot create VHDL directory \"%1\"!")
            .arg(vhdlDir.path()+"/"+lib));
          return;
        }
      vhdlDir.setPath(vhdlDir.path()+"/"+lib);
      QFile destFile;
      destFile.setFileName(vhdlDir.filePath(entity+".vhdl"));
      if(!destFile.open(QIODevice::WriteOnly)) {
        ErrText->appendPlainText(tr("ERROR: Cannot create \"%1\"!")
          .arg(destFile.fileName()));
        return;
      }
      destFile.write(text.toLatin1(), text.length());
      destFile.close();
      Program = pathName(QucsSettings.BinDir + QucsDigiLib);
      Arguments << QucsSettings.tempFilesDir.filePath("netlist.txt")
                << pathName(SimPath)
                << entity
                << lib;
    }
  }
  // Simulate schematic window.
  else {
    // output NodeSets, SPICE simulations etc.
    for(QStringList::Iterator it = Collect.begin(); it != Collect.end(); ++it) {
      // don't put library includes into netlist...
      if ((*it).right(4) != ".lst" &&
        (*it).right(5) != ".vhdl" &&
        (*it).right(4) != ".vhd" &&
        (*it).right(2) != ".v") {
        Stream << *it << '\n';
      }
    }
    Stream << '\n';

    isVerilog = ((Schematic*)DocWidget)->getIsVerilog();
    SimTime = ((Schematic*)DocWidget)->createNetlist(Stream, SimPorts);
    if(SimTime.length()>0&&SimTime.at(0) == '\xA7') {
      NetlistFile.close();
      ErrText->insertPlainText(SimTime.mid(1));
      FinishSimulation(-1);
      return;
    }
    if (isVerilog) {
      Stream << "\n"
       << "  initial begin\n"
       << "    $dumpfile(\"digi.vcd\");\n"
       << "    $dumpvars();\n"
       << "    #" << SimTime << " $finish;\n"
       << "  end\n\n"
       << "endmodule // TestBench\n";
    }
    NetlistFile.close();
    ProgText->insertPlainText(tr("done.\n"));  // of "creating netlist...

    if(SimPorts < 0) {

      // append command arguments
      // append netlist with same arguments
      if (! Module::vaComponents.isEmpty()) {

          /*! Only pass modules to Qucsator that are indeed used on
           * the schematic,it might be the case that the user loaded the icons,
           * but did not compiled the module. Qucsator will not find the library.
           *
           * Check if used symbols have corresponding lib before running
           * Qucsator? Need to search on the netlis.txt? Is there other data
           * structure containing the netlist?
           *
          */
          QStringList usedComponents;

          if (!NetlistFile.open(QIODevice::ReadOnly))
             QMessageBox::critical(this, tr("Error"), tr("Cannot read netlist!"));
          else {
             QString net = QString(NetlistFile.readAll());

             QMapIterator<QString, QString> i(Module::vaComponents);
             while (i.hasNext()) {
                 i.next();
                 if (net.contains(i.key()))
                     usedComponents << i.key();
             }
             NetlistFile.close();
          }

          if (! usedComponents.isEmpty()) {


            /// \todo remove the command line arguments? use only netlist annotation?
            //Arguments << "-p" << QucsSettings.QucsWorkDir.absolutePath()
            //          << "-m" << usedComponents;
            //qDebug() << "Command :" << Program << Arguments.join(" ");

            /// Annotate netlist with Verilog-A dynamic path and module names
            ///
            if (!NetlistFile.open(QFile::Append | QFile::Text))
               QMessageBox::critical(this, tr("Error"), tr("Cannot read netlist!"));
            else {
               QTextStream out(&NetlistFile);
               out << "\n";
               out << "# --path=" << QucsSettings.QucsWorkDir.absolutePath() << "\n";
               out << "# --module=" << usedComponents.join(" ") << "\n";

               NetlistFile.close();
            }
          }
      } // vaComponents not empty

      if((SimOpt = findOptimization((Schematic*)DocWidget))) {
      ((Optimize_Sim*)SimOpt)->createASCOnetlist();

        Program = QucsSettings.AscoBinDir.canonicalPath();
        Program = QDir::toNativeSeparators(Program+"/"+"asco"+QString(executableSuffix));
        Arguments << "-qucs" << QucsSettings.tempFilesDir.filePath("asco_netlist.txt")
                  << "-o" << "asco_out";
      }
      else {
        if (QucsSettings.QucsatorVar.isEmpty()) {
          if (QFileInfo(QucsSettings.Qucsator).isRelative()) {
            Program = QFileInfo(QucsSettings.BinDir + QucsSettings.Qucsator).absoluteFilePath();
          } else {
            Program = QFileInfo(QucsSettings.Qucsator).absoluteFilePath();
          }
        } else Program = QucsSettings.QucsatorVar;
        Arguments << "-b" << "-g" << "-i"
                  << QucsSettings.tempFilesDir.filePath("netlist.txt")
                  << "-o" << DataSet;
      }
    }
    else {
      if (isVerilog) {
          Program = QDir::toNativeSeparators(QucsSettings.BinDir + QucsVeri);
          Arguments << QDir::toNativeSeparators(QucsSettings.tempFilesDir.filePath("netlist.txt"))
                    << DataSet
                    << SimTime
                    << QDir::toNativeSeparators(SimPath)
                    << QDir::toNativeSeparators(QucsSettings.BinDir)
                    << "-c";
      } else {
/// \todo \bug error: unrecognized command line option '-Wl'
#if defined(_WIN32) || defined(__MINGW32__)
    Program = QDir::toNativeSeparators(pathName(QucsSettings.BinDir + QucsDigi));
    Arguments << QDir::toNativeSeparators(QucsSettings.tempFilesDir.filePath("netlist.txt"))
              << DataSet
              << SimTime
              << QDir::toNativeSeparators(SimPath)
              << QDir::toNativeSeparators(QucsSettings.BinDir) << "-Wall" << "-c";
#else
    Program = QDir::toNativeSeparators(pathName(QucsSettings.BinDir + QucsDigi));
    Arguments << QucsSettings.tempFilesDir.filePath("netlist.txt")
              << DataSet << SimTime.remove(" ") << pathName(SimPath)
              << pathName(QucsSettings.BinDir) << "-Wall" << "-c";

#endif
      }
    }
  }

  disconnect(&SimProcess, 0, 0, 0);
  connect(&SimProcess, SIGNAL(readyReadStandardError()), SLOT(slotDisplayErr()));
  connect(&SimProcess, SIGNAL(readyReadStandardOutput()), SLOT(slotDisplayMsg()));
  connect(&SimProcess, SIGNAL(finished(int, QProcess::ExitStatus)),
                       SLOT(slotSimEnded(int, QProcess::ExitStatus)));
  connect(&SimProcess, SIGNAL(stateChanged(QProcess::ProcessState)),
                       SLOT(slotStateChanged(QProcess::ProcessState)));

#ifdef SPEEDUP_PROGRESSBAR
  waitForUpdate = false;
#endif
  wasLF = false;

  ProgressText = "";

#if defined(_WIN32) || defined(__MINGW32__)
  QString sep(";"); // path separator
#else
  QString sep(":");
#endif

  // append process PATH
  // insert Qucs bin dir, so ASCO can find qucsator
  env.insert("PATH", env.value("PATH") + sep + QucsSettings.BinDir );
  if (Program.endsWith(QStringLiteral("asco") + executableSuffix)) {
#ifdef Q_OS_UNIX
    QDir tempDir(QucsSettings.tempFilesDir.absolutePath());
    QString tmpdir = tempDir.filePath("qucs_ascodir"); // ASCO doesn't accept qucsator_rf name
    if (!QDir(tmpdir).exists()) {
        QDir().mkdir(tmpdir);
    }

    // Creating symlink
    QString tmp_qucsator = QDir(tmpdir).filePath("qucsator");
    QString target = QFileInfo(QucsSettings.Qucsator).absoluteFilePath();

    if (QFile::exists(tmp_qucsator)) {
        QFile::remove(tmp_qucsator);
    }
    QFile::link(target, tmp_qucsator);

    // Modify environment variable
    QString sep = ":";
    env.insert("PATH", env.value("PATH") + sep + tmpdir);
#endif
// Only patched version of ASCO works on Windows,
// because qucsator name is hardcoded inside ASCO sources
#ifdef Q_OS_WIN
    env.insert("ASCO_SIM_PATH",QucsSettings.Qucsator);
#endif
  }
  SimProcess.setProcessEnvironment(env);

  qDebug() << "Command :" << Program << Arguments.join(" ");
  SimProcess.start(Program, Arguments); // launch the program

}

// ------------------------------------------------------------------------
Component * SimMessage::findOptimization(Schematic *Doc) {
  Component *pc;
  for(pc=Doc->a_Components->first(); pc!=0; pc=Doc->a_Components->next())
    if(pc->isActive)
      if(pc->Model == ".Opt")
	return pc;
  return NULL;
}


/*!
 * \brief called when the process sends an output to stdout.
 */
void SimMessage::slotDisplayMsg()
{
  int i;
  ProgressText += QString(SimProcess.readAllStandardOutput());
  if(wasLF) {
    i = ProgressText.lastIndexOf('\r');
    if(i > 1) {
#ifdef SPEEDUP_PROGRESSBAR
      iProgress = 10*int(ProgressText.at(i-2).toLatin1()-'0') +
                     int(ProgressText.at(i-1).toLatin1()-'0');
      if(!waitForUpdate) {
        QTimer::singleShot(20, this, SLOT(slotUpdateProgressBar()));
        waitForUpdate = true;
      }
#else
      SimProgress->setMaximum(100);
      int value = 10*int(ProgressText.at(i-2).toLatin1()-'0') +
              int(ProgressText.at(i-1).toLatin1()-'0');
      SimProgress->setValue(value);
      emit progressBarChanged(value);
#endif
      ProgressText.remove(0, i+1);
    }

    if(ProgressText.size()>0&&ProgressText.at(0).toLatin1() <= '\t')
      return;
  }
  else {
    i = ProgressText.indexOf('\t'); // marker for progress indicator
    if(i >= 0) {
      wasLF = true;
      QString tmps = ProgressText.left(i).trimmed();
      if (!tmps.isEmpty()) // avoid adding a newline if no text to show
        ProgText->appendPlainText(tmps);
      ProgressText.remove(0, i+1);
      return;
    }
  }

  QString tmps = ProgressText.trimmed();
  if (!tmps.isEmpty()) // avoid adding a newline if no text to show
    ProgText->appendPlainText(tmps);
  ProgressText = "";
  wasLF = false;
}

#ifdef SPEEDUP_PROGRESSBAR
// ------------------------------------------------------------------------
void SimMessage::slotUpdateProgressBar()
{
  SimProgress->setProgress(iProgress, 100);
  waitForUpdate = false;
}
#endif

/*!
 * \brief Insert process stderr output in the Error Message output window.
 *
 *  Called when the process sends an output to stderr.
 */
void SimMessage::slotDisplayErr()
{
  ErrText->appendPlainText(QString(SimProcess.readAllStandardError()));
}

/*!
 * \brief The process state changes.
 *
 *  Called when the process changes state;
 *  \param[in] newState new status of the process
 */
void SimMessage::slotStateChanged(QProcess::ProcessState newState)
{
  static QProcess::ProcessState oldState;
  qDebug() << "SimMessage::slotStateChanged() : newState = " << newState
           << " " << SimProcess.error();
  switch(newState){
    case QProcess::NotRunning:
      switch(SimProcess.error()){
        case QProcess::FailedToStart: // does not happen (?)
        case QProcess::UnknownError: // getting here instead
          switch(oldState){
            case QProcess::Starting: // failed to start.
              ErrText->insertPlainText(tr("ERROR: Cannot start ") + Program +
                  " (" + SimProcess.errorString() + ")\n");
              FinishSimulation(-1);
              break;
            case QProcess::Running:
              // process ended without trouble.
              // slotSimEnded will be invoked soon.
              break;
            case QProcess::NotRunning:
              // impossible.
              break;
          }
          break;
        // note that on Windows negative exit codes are treated as 'crash'
        //   see comments in slotSimEnded() to handle this properly
        case QProcess::Crashed:
        case QProcess::Timedout:
        case QProcess::WriteError:
        case QProcess::ReadError:
          // nothing (yet)
          break;
      }
    break;
    case QProcess::Starting:
          ProgText->insertPlainText(tr("Starting ") + Program + "\n");
    break;
    case QProcess::Running:
    break;
  }
  oldState = newState;
}

/*!
 * \brief Check the simulation process exit status.
 *
 *  Called when the simulation process terminates; inserts an error
 *  message in the Error Message output window in case the simulation
 *  process does not exit normally.
 *  \param[in] exitCode exit code of the process
 *  \param[in] exitStatus exit status of the process
 *  \todo use a macro for the bugs report email (needs to define it for CMake)
 */
void SimMessage::slotSimEnded(int exitCode, QProcess::ExitStatus exitStatus )
{
  int stat = exitCode;

  if ((exitStatus != QProcess::NormalExit) &&
#if defined(_WIN32) || defined(__MINGW32__)
/*! \todo:  due to a bug in Qt, negative error codes are erroneously interpreted
            as "program crashed", see https://bugreports.qt.io/browse/QTBUG-28735
            When we will switch to Qt5(.1) this code can be removed...*/
      (uint)stat >= 0x80000000U && (uint)stat < 0xD0000000U &&
#endif
      !simKilled) { // as when killed by user exitStatus will be QProcess::CrashExit
    stat = -1;
    ErrText->appendPlainText(tr("ERROR: Simulator crashed!"));
    ErrText->appendPlainText(tr("Please report this error to qucs-bugs@lists.sourceforge.net"));
  }
  FinishSimulation(stat); // 0 = normal , !=0 = error
}

/*!
 * \brief Add end-of-simulation messages and save the relevant data.
 *
 *  Called when the simulation ended with errors before starting the
 *  simulator process.
 *  \param[in] Status exit status of the process (0 = normal, !=0 = error)
 */
void SimMessage::FinishSimulation(int Status)
{
  Abort->setText(tr("Close window"));
  Display->setDisabled(false);
  SimProgress->setValue(100);  // progress bar to 100%

  QDate d = QDate::currentDate();   // get date of today
  QTime t = QTime::currentTime();   // get time

  if(Status == 0) {
    QString txt = tr("Simulation ended on %1 at %2").
      arg(d.toString("ddd dd. MMM yyyy")).
      arg(t.toString("hh:mm:ss:zzz"));
    ProgText->appendPlainText("\n" + txt + "\n" + tr("Ready."));
  }
  else {
    QString txt = tr("Errors occurred during simulation on %1 at %2").
      arg(d.toString("ddd dd. MMM yyyy")).
      arg(t.toString("hh:mm:ss:zzz"));
    ProgText->appendPlainText("\n" + txt + "\n" + tr("Aborted."));
  }

  QFile file(QucsSettings.tempFilesDir.filePath("log.txt"));  // save simulator messages
  if(file.open(QIODevice::WriteOnly)) {
    QTextStream stream(&file);
    stream << tr("Output:\n-------") << "\n\n";
    for(int z=0; z<ProgText->document()->blockCount(); z++)
      stream << ProgText->document()->findBlockByNumber(z).text() << "\n";
    stream << "\n\n\n" <<
      tr("Errors and Warnings:\n--------------------") << "\n\n";
    for(int z=0; z<ErrText->document()->blockCount(); z++)
      stream << ErrText->document()->findBlockByNumber(z).text() << "\n";
    file.close();
  }

  if(Status == 0) {
    if(SimOpt) { // save optimization data
      QFile ifile(QucsSettings.tempFilesDir.filePath("asco_out.dat"));
      QFile ofile(DataSet);
      if(ifile.open(QIODevice::ReadOnly)) {
        if(ofile.open(QIODevice::WriteOnly)) {
          QByteArray data = ifile.readAll();
          ofile.write(data);
          ofile.close();
        }
        ifile.close();
      }
      if(((Optimize_Sim*)SimOpt)->loadASCOout())
        ((Schematic*)DocWidget)->setChanged(true,true);
    }
  }

  emit SimulationEnded(Status, this);
}

/*!
 * \brief Close and delete simulation dialog
 *
 * To call accept(), which is protected, from the outside.
 * Called from the main GUI.
 */
void SimMessage::slotClose()
{
  accept();
}

// ------------------------------------------------------------------------
void SimMessage::slotDisplayButton()
{
  emit displayDataPage(DocName, DataDisplay);
  accept();
}

void SimMessage::AbortSim()
{
  ErrText->appendPlainText(tr("Simulation aborted by the user!"));
  simKilled = true;
  SimProcess.kill();
}

/* \brief Allows the doc Widget to be set after the constructor
 *
 *  Useful for creating only one SimMessage dialog
 */
void SimMessage::setDocWidget(QWidget *w)
{
    this->DocWidget = w;

    QucsDoc *Doc;
    DocWidget = w;
    if(QucsApp::isTextDocument(DocWidget))
      Doc = (QucsDoc*) ((TextDoc*)DocWidget);
    else
      Doc = (QucsDoc*) ((Schematic*)DocWidget);

    DocName = Doc->getDocName();
    DataDisplay = Doc->getDataDisplay();
    Script = Doc->getScript();
    QFileInfo Info(DocName);
    DataSet = QDir::toNativeSeparators(Info.path()) +
      QDir::separator() + Doc->getDataSet();
    showBias = Doc->getShowBias();     // save some settings as the document...
    SimOpenDpl = Doc->getSimOpenDpl(); // ...could be closed during the simulation.
    SimRunScript = Doc->getSimRunScript();
}