Qucs-GUI
0.0.19
|
00001 /*************************************************************************** 00002 spicefile.cpp 00003 --------------- 00004 begin : Tue Dez 28 2004 00005 copyright : (C) 2004 by Michael Margraf 00006 email : michael.margraf@alumni.tu-berlin.de 00007 ***************************************************************************/ 00008 00009 /*************************************************************************** 00010 * * 00011 * This program is free software; you can redistribute it and/or modify * 00012 * it under the terms of the GNU General Public License as published by * 00013 * the Free Software Foundation; either version 2 of the License, or * 00014 * (at your option) any later version. * 00015 * * 00016 ***************************************************************************/ 00017 00018 #if HAVE_UNISTD_H 00019 # include <unistd.h> 00020 #endif 00021 #include <QRegExp> 00022 #include <QProcess> 00023 #include <QString> 00024 #include <QStringList> 00025 #include <QMessageBox> 00026 #include <QTextStream> 00027 #include <QFile> 00028 #include <QDir> 00029 #include <QFileInfo> 00030 #include <QMutex> 00031 #include <QDebug> 00032 #include <QStatusBar> 00033 00034 #include "spicefile.h" 00035 #include "schematic.h" 00036 #include "main.h" 00037 #include "qucs.h" 00038 #include "misc.h" 00039 00040 00041 SpiceFile::SpiceFile() 00042 { 00043 Description = QObject::tr("SPICE netlist file"); 00044 // Property descriptions not needed, but must not be empty ! 00045 Props.append(new Property("File", "", true, QString("x"))); 00046 Props.append(new Property("Ports", "", false, QString("x"))); 00047 Props.append(new Property("Sim", "yes", false, QString("x"))); 00048 Props.append(new Property("Preprocessor", "none", false, QString("x"))); 00049 withSim = false; 00050 00051 Model = "SPICE"; 00052 Name = "X"; 00053 changed = false; 00054 00055 // Do NOT call createSymbol() here. But create port to let it rotate. 00056 Ports.append(new Port(0, 0)); 00057 } 00058 00059 // ------------------------------------------------------- 00060 Component* SpiceFile::newOne() 00061 { 00062 SpiceFile *p = new SpiceFile(); 00063 p->recreate(0); // createSymbol() is NOT called in constructor !!! 00064 return p; 00065 } 00066 00067 // ------------------------------------------------------- 00068 Element* SpiceFile::info(QString& Name, char* &BitmapFile, bool getNewOne) 00069 { 00070 Name = QObject::tr("SPICE netlist"); 00071 BitmapFile = (char *) "spicefile"; 00072 00073 if(getNewOne) { 00074 SpiceFile *p = new SpiceFile(); 00075 p->recreate(0); // createSymbol() is NOT called in constructor !!! 00076 return p; 00077 } 00078 return 0; 00079 } 00080 00081 // ------------------------------------------------------- 00082 void SpiceFile::createSymbol() 00083 { 00084 QFont f = QucsSettings.font; // get the basic font 00085 // symbol text is smaller (10 pt default) 00086 f.setPointSize(10); 00087 // use the screen-compatible metric 00088 QFontMetrics smallmetrics(f, 0); // get size of text 00089 int fHeight = smallmetrics.lineSpacing(); 00090 00091 int No = 0; 00092 QString tmp, PortNames = Props.at(1)->Value; 00093 if(!PortNames.isEmpty()) No = PortNames.count(',') + 1; 00094 00095 // draw symbol outline 00096 #define HALFWIDTH 17 00097 int h = 30*((No-1)/2) + 15; 00098 Lines.append(new Line(-HALFWIDTH, -h, HALFWIDTH, -h,QPen(Qt::darkBlue,2))); 00099 Lines.append(new Line( HALFWIDTH, -h, HALFWIDTH, h,QPen(Qt::darkBlue,2))); 00100 Lines.append(new Line(-HALFWIDTH, h, HALFWIDTH, h,QPen(Qt::darkBlue,2))); 00101 Lines.append(new Line(-HALFWIDTH, -h,-HALFWIDTH, h,QPen(Qt::darkBlue,2))); 00102 00103 int w, i = fHeight/2; 00104 if(withSim) { 00105 i = fHeight - 2; 00106 tmp = QObject::tr("sim"); 00107 w = smallmetrics.width(tmp); 00108 Texts.append(new Text(w/-2, 0, tmp, Qt::red)); 00109 } 00110 tmp = QObject::tr("spice"); 00111 w = smallmetrics.boundingRect(tmp).width(); 00112 Texts.append(new Text(w/-2, -i, tmp)); 00113 00114 i = 0; 00115 int y = 15-h; 00116 while(i<No) { // add ports lines and numbers 00117 Lines.append(new Line(-30, y,-HALFWIDTH, y,QPen(Qt::darkBlue,2))); 00118 Ports.append(new Port(-30, y)); 00119 tmp = PortNames.section(',', i, i).mid(4); 00120 w = smallmetrics.width(tmp); 00121 Texts.append(new Text(-20-w, y-fHeight-2, tmp)); // text right-aligned 00122 i++; 00123 00124 if(i == No) break; // if odd number of ports there will be one port less on the right side 00125 Lines.append(new Line(HALFWIDTH, y, 30, y,QPen(Qt::darkBlue,2))); 00126 Ports.append(new Port( 30, y)); 00127 tmp = PortNames.section(',', i, i).mid(4); 00128 Texts.append(new Text( 20, y-fHeight-2, tmp)); // text left-aligned 00129 y += 60; 00130 i++; 00131 } 00132 00133 if(No > 0) { 00134 Lines.append(new Line( 0, h, 0,h+15,QPen(Qt::darkBlue,2))); 00135 Texts.append(new Text( 4, h,"Ref")); 00136 Ports.append(new Port( 0, h+15)); // 'Ref' port 00137 } 00138 00139 x1 = -30; y1 = -h-2; 00140 x2 = 30; y2 = h+15; 00141 00142 // compute component name text position - normal size font 00143 QFontMetrics metrics(QucsSettings.font, 0); // use the screen-compatible metric 00144 fHeight = metrics.lineSpacing(); 00145 tx = x1+4; 00146 ty = y1 - fHeight - 4; 00147 if(Props.first()->display) ty -= fHeight; 00148 changed = true; 00149 } 00150 00151 // --------------------------------------------------- 00152 QString SpiceFile::netlist() 00153 { 00154 if(Props.at(1)->Value.isEmpty()) 00155 return QString(""); // no ports, no subcircuit instance 00156 00157 QString s = "Sub:"+Name; // SPICE netlist is subcircuit 00158 foreach(Port *pp, Ports) 00159 s += " "+pp->Connection->Name; // output all node names 00160 00161 QString f = misc::properFileName(Props.first()->Value); 00162 s += " Type=\""+misc::properName(f)+"\"\n"; 00163 return s; 00164 } 00165 00166 // ------------------------------------------------------- 00167 QString SpiceFile::getSubcircuitFile() 00168 { 00169 // construct full filename 00170 QString FileName = Props.getFirst()->Value; 00171 00172 if (FileName.isEmpty()) 00173 { 00174 return misc::properAbsFileName(FileName); 00175 } 00176 00177 QFileInfo FileInfo(FileName); 00178 00179 if (FileInfo.exists()) 00180 { 00181 // the file must be an absolute path to a schematic file 00182 return FileInfo.absoluteFilePath(); 00183 } 00184 else 00185 { 00186 // get the complete base name (everything except the last '.' 00187 // and whatever follows 00188 QString baseName = FileInfo.completeBaseName(); 00189 00190 // if only a file name is supplied, first check if it is in the 00191 // same directory as the schematic file it is a part of 00192 if (FileInfo.fileName () == FileName) 00193 { 00194 // the file has no path information, just the file name 00195 if (containingSchematic) 00196 { 00197 // check if a file of the same name is in the same directory 00198 // as the schematic file, if we have a pointer to it, in 00199 // which case we use this one 00200 QFileInfo schematicFileInfo = containingSchematic->getFileInfo (); 00201 00202 for (int i = 0; i < QucsSettings.spiceExtensions.count (); i++) 00203 { 00204 QString extension = QucsSettings.spiceExtensions[i]; 00205 extension.remove(0, 1); // take leading '*' out, issue with exits() 00206 00207 QFileInfo localFileInfo (schematicFileInfo.canonicalPath () 00208 + "/" + baseName + extension); 00209 00210 if (localFileInfo.exists ()) 00211 { 00212 // return the subcircuit saved in the same directory 00213 // as the schematic file 00214 return localFileInfo.absoluteFilePath(); 00215 } 00216 else 00217 { 00219 qCritical() << "Spice file not found:" << localFileInfo.absFilePath(); 00220 } 00221 } 00222 } 00223 } 00224 00225 // look up the hash table for the schematic file as 00226 // it does not seem to be an absolute path, this will also 00227 // search the home directory which is always hashed 00228 QMutex mutex; 00229 mutex.lock(); 00230 QString hashsearchresult = ""; 00231 // if GUI is running and has something in the hash 00232 if ( (QucsMain != 0) && !QucsMain->spiceNameHash.isEmpty() ) 00233 hashsearchresult = QucsMain->spiceNameHash.value(baseName); 00234 mutex.unlock(); 00235 00236 if (hashsearchresult.isEmpty()) 00237 { 00238 // the schematic was not found in the hash table, return 00239 // what would always have been returned in this case 00240 return misc::properAbsFileName(FileName); 00241 } 00242 else 00243 { 00244 // we found an entry in the hash table, check it actually still exists 00245 FileInfo.setFile(hashsearchresult); 00246 00247 if (FileInfo.exists()) 00248 { 00249 // it does exist so return the absolute file path 00250 return FileInfo.absoluteFilePath(); 00251 } 00252 else 00253 { 00254 // the schematic file does not actually exist, return 00255 // what would always have been returned in this case 00256 return misc::properAbsFileName(FileName); 00257 } 00258 } 00259 00260 } 00261 00262 } 00263 00264 // ------------------------------------------------------------------------- 00265 bool SpiceFile::createSubNetlist(QTextStream *stream) 00266 { 00267 // check file name 00268 QString FileName = Props.first()->Value; 00269 if(FileName.isEmpty()) { 00270 ErrText += QObject::tr("ERROR: No file name in SPICE component \"%1\"."). 00271 arg(Name); 00272 return false; 00273 } 00274 00275 // check input and output file 00276 QFile SpiceFile, ConvFile; 00277 FileName = getSubcircuitFile(); 00278 SpiceFile.setFileName(FileName); 00279 if(!SpiceFile.open(QIODevice::ReadOnly)) { 00280 ErrText += QObject::tr("ERROR: Cannot open SPICE file \"%1\"."). 00281 arg(FileName); 00282 return false; 00283 } 00284 SpiceFile.close(); 00285 QString ConvName = SpiceFile.fileName() + ".lst"; 00286 ConvFile.setFileName(ConvName); 00287 QFileInfo Info(ConvName); 00288 00289 // re-create converted file if necessary 00290 if(changed || !ConvFile.exists() || 00291 (lastLoaded.isValid() && lastLoaded < Info.lastModified())) { 00292 if(!ConvFile.open(QIODevice::WriteOnly)) { 00293 ErrText += QObject::tr("ERROR: Cannot save converted SPICE file \"%1\"."). 00294 arg(FileName + ".lst"); 00295 return false; 00296 } 00297 outstream = stream; 00298 filstream = new QTextStream(&ConvFile); 00299 QString SpiceName = SpiceFile.fileName(); 00300 bool ret = recreateSubNetlist(&SpiceName, &FileName); 00301 ConvFile.close(); 00302 delete filstream; 00303 return ret; 00304 } 00305 00306 // load old file and stuff into stream 00307 if(!ConvFile.open(QIODevice::ReadOnly)) { 00308 ErrText += QObject::tr("ERROR: Cannot open converted SPICE file \"%1\"."). 00309 arg(FileName + ".lst"); 00310 return false; 00311 } 00312 QByteArray FileContent = ConvFile.readAll(); 00313 ConvFile.close(); 00314 //? stream->writeRawBytes(FileContent.data(), FileContent.size()); 00315 (*stream) << FileContent.data(); 00316 return true; 00317 } 00318 00319 // ------------------------------------------------------------------------- 00320 bool SpiceFile::recreateSubNetlist(QString *SpiceFile, QString *FileName) 00321 { 00322 // initialize collectors 00323 ErrText = ""; 00324 NetText = ""; 00325 SimText = ""; 00326 NetLine = ""; 00327 00328 // evaluate properties 00329 if(Props.at(1)->Value != "") 00330 makeSubcircuit = true; 00331 else 00332 makeSubcircuit = false; 00333 if(Props.at(2)->Value == "yes") 00334 insertSim = true; 00335 else 00336 insertSim = false; 00337 00338 // preprocessor run if necessary 00339 QString preprocessor = Props.at(3)->Value; 00340 if (preprocessor != "none") { 00341 bool piping = true; 00342 QStringList script; 00343 #ifdef __MINGW32__ 00344 QString interpreter = "tinyperl.exe"; 00345 #else 00346 QString interpreter = "perl"; 00347 #endif 00348 if (preprocessor == "ps2sp") { 00349 script << "ps2sp"; 00350 } else if (preprocessor == "spicepp") { 00351 script << "spicepp.pl"; 00352 } else if (preprocessor == "spiceprm") { 00353 script << "spiceprm"; 00354 piping = false; 00355 } 00356 SpicePrep = new QProcess(this); 00357 script << interpreter; 00358 script << script; 00359 script << *SpiceFile; 00360 00361 QFile PrepFile; 00362 QString PrepName = *SpiceFile + ".pre"; 00363 00364 if (!piping) { 00365 script << PrepName; 00366 connect(SpicePrep, SIGNAL(readyReadStandardOutput()), SLOT(slotSkipOut())); 00367 connect(SpicePrep, SIGNAL(readyReadStandardError()), SLOT(slotGetPrepErr())); 00368 } else { 00369 connect(SpicePrep, SIGNAL(readyReadStandardOutput()), SLOT(slotGetPrepOut())); 00370 connect(SpicePrep, SIGNAL(readyReadStandardError()), SLOT(slotGetPrepErr())); 00371 } 00372 00373 QMessageBox *MBox = 00374 new QMessageBox(QMessageBox::NoIcon, 00375 QObject::tr("Info"), 00376 QObject::tr("Preprocessing SPICE file \"%1\".").arg(*SpiceFile), 00377 QMessageBox::Abort); 00378 MBox->setAttribute(Qt::WA_DeleteOnClose); 00379 connect(SpicePrep, SIGNAL(finished(int)), MBox, SLOT(close())); 00380 00381 if (piping) { 00382 PrepFile.setFileName(PrepName); 00383 if(!PrepFile.open(QIODevice::WriteOnly)) { 00384 ErrText += 00385 QObject::tr("ERROR: Cannot save preprocessed SPICE file \"%1\"."). 00386 arg(PrepName); 00387 return false; 00388 } 00389 prestream = new QTextStream(&PrepFile); 00390 } 00391 00392 QProcessEnvironment env = QProcessEnvironment::systemEnvironment(); 00393 env.insert("PATH", env.value("PATH") ); 00394 SpicePrep->setProcessEnvironment(env); 00395 SpicePrep->start(script.join(" ")); 00396 //QucsHelp->setCommunication(0); 00397 00398 if(SpicePrep->state()!=QProcess::Running&& 00399 SpicePrep->state()!=QProcess::Starting) { 00400 ErrText += QObject::tr("ERROR: Cannot execute \"%1\"."). 00401 arg(interpreter + " " + script.join(" ") + "\"."); 00402 if (piping) { 00403 PrepFile.close(); 00404 delete prestream; 00405 } 00406 return false; 00407 } 00408 //SpicePrep->closeStdin(); 00409 00410 MBox->exec(); 00411 delete SpicePrep; 00412 if (piping) { 00413 PrepFile.close(); 00414 delete prestream; 00415 } 00416 *SpiceFile = PrepName; 00417 } 00418 00419 // begin command line construction 00420 QString prog; 00421 QStringList com; 00422 //prog = QucsSettings.BinDir + "qucsconv" + executableSuffix; 00423 prog = QucsSettings.Qucsconv; 00424 00425 if(makeSubcircuit) com << "-g" << "_ref"; 00426 com << "-if" << "spice" << "-of" << "qucs"; 00427 com << "-i" << *SpiceFile; 00428 00429 // begin netlist text creation 00430 if(makeSubcircuit) { 00431 QString f = misc::properFileName(*FileName); 00432 NetText += "\n.Def:" + misc::properName(f) + " "; 00433 QString PortNames = Props.at(1)->Value; 00434 PortNames.replace(',', ' '); 00435 NetText += PortNames; 00436 if(makeSubcircuit) NetText += " _ref"; 00437 } 00438 NetText += "\n"; 00439 00440 // startup SPICE conversion process 00441 QucsConv = new QProcess(this); 00442 QProcessEnvironment env = QProcessEnvironment::systemEnvironment(); 00443 env.insert("PATH", env.value("PATH") ); 00444 QucsConv->setProcessEnvironment(env); 00445 00446 qDebug() << "SpiceFile::recreateSubNetlist :Command:" << prog << com.join(" "); 00447 // QucsConv->start(com.join(" ")); 00448 QucsConv->start(prog, com); 00449 00451 connect(QucsConv, SIGNAL(readyReadStandardOutput()), SLOT(slotGetNetlist())); 00452 connect(QucsConv, SIGNAL(readyReadStandardError()), SLOT(slotGetError())); 00453 connect(QucsConv, SIGNAL(finished(int,QProcess::ExitStatus)), SLOT(slotExited())); 00454 00455 if(QucsConv->state()!=QProcess::Running&& 00456 QucsConv->state()!=QProcess::Starting) { 00457 ErrText += QObject::tr("COMP ERROR: Cannot start QucsConv!"); 00458 return false; 00459 } 00460 (*outstream) << NetText; 00461 (*filstream) << NetText; 00462 00463 // only interact with the GUI if it was launched 00464 if (QucsMain) { 00465 QucsMain->statusBar()->showMessage(tr("Converting SPICE file \"%1\".").arg(*SpiceFile), 2000); 00466 } 00467 else 00468 qDebug() << QObject::tr("Converting SPICE file \"%1\".").arg(*SpiceFile); 00469 00470 // finish 00471 QucsConv->waitForFinished(); 00472 delete QucsConv; 00473 lastLoaded = QDateTime::currentDateTime(); 00474 return true; 00475 } 00476 00477 // ------------------------------------------------------------------------- 00478 void SpiceFile::slotSkipErr() 00479 { 00480 SpicePrep->readAllStandardError(); 00481 } 00482 00483 // ------------------------------------------------------------------------- 00484 void SpiceFile::slotSkipOut() 00485 { 00486 SpicePrep->readAllStandardOutput(); 00487 } 00488 00489 // ------------------------------------------------------------------------- 00490 void SpiceFile::slotGetPrepErr() 00491 { 00492 ErrText += QString(SpicePrep->readAllStandardError()); 00493 } 00494 00495 // ------------------------------------------------------------------------- 00496 void SpiceFile::slotGetPrepOut() 00497 { 00498 (*prestream) << QString(SpicePrep->readAllStandardOutput()); 00499 } 00500 00501 // ------------------------------------------------------------------------- 00502 void SpiceFile::slotGetError() 00503 { 00504 ErrText += QString(QucsConv->readAllStandardError()); 00505 } 00506 00507 // ------------------------------------------------------------------------- 00508 void SpiceFile::slotGetNetlist() 00509 { 00510 int i; 00511 QString s; 00512 NetLine += QString(QucsConv->readAllStandardOutput()); 00513 00514 while((i = NetLine.indexOf('\n')) >= 0) { 00515 s = NetLine.left(i); 00516 NetLine.remove(0, i+1); 00517 s = s.trimmed(); 00518 if(s.size()>0&&s.at(0) == '#') { 00519 continue; 00520 } else if(s.isEmpty()) { 00521 continue; 00522 } else if(s.size()>0&&s.at(0) == '.') { 00523 if(s.left(5) != ".Def:") { 00524 if(insertSim) SimText += s + "\n"; 00525 continue; 00526 } 00527 } 00528 if(makeSubcircuit) { 00529 (*outstream) << " "; 00530 (*filstream) << " "; 00531 } 00532 (*outstream) << s << "\n"; 00533 (*filstream) << s << "\n"; 00534 } 00535 } 00536 00537 // ------------------------------------------------------------------------- 00538 void SpiceFile::slotExited() 00539 { 00540 if (QucsConv->exitStatus() != QProcess::NormalExit) { 00541 NetText = ""; 00542 } 00543 else { 00544 if(makeSubcircuit) { 00545 (*outstream) << ".Def:End\n\n"; 00546 (*filstream) << ".Def:End\n\n"; 00547 } 00548 if(!SimText.isEmpty()) { 00549 (*outstream) << SimText; 00550 (*filstream) << SimText; 00551 } 00552 } 00553 }