/*************************************************************************** qucs.cpp ---------- begin : Thu Aug 28 2003 copyright : (C) 2003, 2004, 2005, 2006 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. * * * ***************************************************************************/ #ifdef HAVE_CONFIG_H # include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "main.h" #include "qucs.h" #include "qucsdoc.h" #include "textdoc.h" #include "schematic.h" #include "mouseactions.h" #include "messagedock.h" #include "settings.h" #include "wire.h" #include "module.h" #include "projectView.h" #include "components/component.h" #include "components/vacomponent.h" #include "components/vhdlfile.h" #include "components/verilogfile.h" #include "components/subcircuit.h" #include "dialogs/savedialog.h" #include "dialogs/newprojdialog.h" #include "dialogs/settingsdialog.h" #include "dialogs/digisettingsdialog.h" #include "dialogs/vasettingsdialog.h" #include "dialogs/qucssettingsdialog.h" #include "dialogs/searchdialog.h" #include "dialogs/sweepdialog.h" #include "dialogs/labeldialog.h" #include "dialogs/matchdialog.h" #include "dialogs/simmessage.h" #include "dialogs/exportdialog.h" #include "dialogs/displaydialog.h" //#include "dialogs/vtabwidget.h" //#include "dialogs/vtabbeddockwidget.h" #include "extsimkernels/externsimdialog.h" #include "dialogs/tuner.h" #include "octave_window.h" #include "printerwriter.h" #include "imagewriter.h" #include "qucslib_common.h" #include "misc.h" #include "extsimkernels/verilogawriter.h" #include "extsimkernels/simsettingsdialog.h" //#include "extsimkernels/codemodelgen.h" #include "symbolwidget.h" QucsApp::QucsApp() { windowTitle = misc::getWindowTitle(); setWindowTitle(windowTitle); QucsSettings.hasDarkTheme = misc::isDarkTheme(); // Instantiate settings singleton and restore window geometry. const auto geometry = _settings::Get().item("MainWindowGeometry"); if (!geometry.isEmpty()) { qDebug() << "Saved geometry is: " << geometry; restoreGeometry(geometry); } // Set a default size and position if geometry does not yet exist. else { QSize size = QGuiApplication::primaryScreen()->size(); int w = size.width(); int h = size.height(); setGeometry (w * 0.25, h * 0.25, w * 0.5, h * 0.5); } QucsFileFilter = tr("Schematic") + " (*.sch);;" + tr("Symbol only") + " (*.sym);;" + tr("Data Display") + " (*.dpl);;" + tr("Qucs Documents") + " (*.sch *.dpl);;" + tr("VHDL Sources") + " (*.vhdl *.vhd);;" + tr("Verilog Sources") + " (*.v);;" + tr("Verilog-A Sources") + " (*.va);;" + tr("Octave Scripts") + " (*.m *.oct);;" + tr("Spice Files") + QStringLiteral(" (") + QucsSettings.spiceExtensions.join(" ") + QStringLiteral(");;") + tr("Any File")+" (*)"; //updateSchNameHash(); //updateSpiceNameHash(); MouseMoveAction = 0; MousePressAction = 0; MouseReleaseAction = 0; MouseDoubleClickAction = 0; initView(); initActions(); initMenuBar(); fillSimulatorsComboBox(); initToolBar(); initStatusBar(); viewBrowseDock->setChecked(true); slotViewOctaveDock(false); slotUpdateRecentFiles(); initCursorMenu(); //Module::registerModules (); // instance of small text search dialog SearchDia = new SearchDialog(this); TuningMode = false; // creates a document called "untitled" Schematic *d = new Schematic(this, ""); int i = addDocumentTab(d); DocumentTab->setCurrentIndex(i); select->setChecked(true); // switch on the 'select' action switchSchematicDoc(true); // "untitled" document is schematic QDir homeDir = QDir::homePath(); lastExportFilename = homeDir.absoluteFilePath("export.png"); // load documents given as command line arguments for(int z=1; zarguments().size(); z++) { QString arg = qApp->arguments()[z]; QByteArray ba = arg.toLatin1(); const char *c_arg = ba.data(); if(*(c_arg) != '-') { QFileInfo Info(arg); QucsSettings.QucsWorkDir.setPath(Info.absoluteDir().absolutePath()); arg = QucsSettings.QucsWorkDir.filePath(Info.fileName()); gotoPage(arg); } } QDir QucsBinDir(QucsSettings.BinDir); if (QucsSettings.firstRun) { // try to find Ngspice QString ngspice_exe_name = "ngspice"; #ifdef Q_OS_WIN ngspice_exe_name+="_con"; #endif /* search own path */ #ifdef Q_OS_MACOS QString ngspice_exe1 = QStandardPaths::findExecutable(ngspice_exe_name,{"/opt/homebrew/bin"}); #else QString ngspice_exe1 = QStandardPaths::findExecutable(ngspice_exe_name,{QucsBinDir.absolutePath()}); #endif /* search system path */ QString ngspice_exe2 = QStandardPaths::findExecutable(ngspice_exe_name); /* search own path */ QString qucsator_exe1 = QStandardPaths::findExecutable("qucsator_rf",{QucsBinDir.absolutePath()}); /* search system path */ QString qucsator_exe2 = QStandardPaths::findExecutable("qucsator_rf"); QString ngspice_exe; bool ngspice_found = false; if(!ngspice_exe1.isEmpty()){ ngspice_found = true; ngspice_exe = ngspice_exe1; }else if(!ngspice_exe2.isEmpty()){ ngspice_found = true; ngspice_exe = ngspice_exe2; } QString qucsator_exe; bool qucsator_found = false; if(!qucsator_exe1.isEmpty()){ qucsator_found = true; qucsator_exe = qucsator_exe1; }else if(!qucsator_exe2.isEmpty()){ qucsator_found = true; qucsator_exe = qucsator_exe2; } QString info_string; if (ngspice_found) { QucsSettings.DefaultSimulator = spicecompat::simNgspice; QucsSettings.NgspiceExecutable = ngspice_exe; info_string += tr("Ngspice found at: ") + ngspice_exe + "\n"; } if(qucsator_found){ QucsSettings.Qucsator = qucsator_exe; info_string += tr("QucsatorRF found at: ") + qucsator_exe + "\n"; } info_string += tr("\nYou can specify another location later" " using Simulation->Simulators Setings\n"); if (!ngspice_found && qucsator_found) { QucsSettings.DefaultSimulator = spicecompat::simQucsator; info_string += tr("\nNOTE: Only QucsatorRF found. This simulator is not" " recommended for general purpose schematics. " " Please install Ngspice."); } if (ngspice_found || qucsator_found) { QMessageBox::information(nullptr,tr("Set simulator"), info_string); fillSimulatorsComboBox(); } else { #ifdef Q_OS_WIN QucsSettings.NgspiceExecutable = "ngspice_con.exe"; QucsSettings.Qucsator = "qucsator_rf.exe"; #else QucsSettings.NgspiceExecutable = "ngspice"; QucsSettings.Qucsator = "qucsator_rf"; #endif QMessageBox::information(this,tr("Qucs"),tr("No simulators found automatically. Please specify simulators" " in the next dialog window.")); slotSimSettings(); } QucsSettings.firstRun = false; } else { if (!QucsSettings.Qucsator.contains("qucsator_rf")) { QucsSettings.Qucsator = QStandardPaths::findExecutable("qucsator_rf",{QucsBinDir.absolutePath()}); if(!QucsSettings.Qucsator.isEmpty()){ QMessageBox::information(this, "Qucs", tr("QucsatorRF found at: ") + QucsSettings.Qucsator + "\n"); } } } // fillLibrariesTreeView(); } QucsApp::~QucsApp() { Module::unregisterModules (); } // ####################################################################### // ########## ########## // ########## Creates the working area (QTabWidget etc.) ########## // ########## ########## // ####################################################################### /** * @brief QucsApp::initView Setup the layour of all widgets */ void QucsApp::initView() { // set application icon // APPLE sets the QApplication icon with Info.plist #ifndef __APPLE__ setWindowIcon (QPixmap(QStringLiteral(":/bitmaps/hicolor/scalable/apps/qucs.svg"))); #else // setUnifiedTitleAndToolBarOnMac(true); setStyleSheet("QToolButton { padding: 0px; }"); #endif DocumentTab = new QTabWidget(this); #if __APPLE__ DocumentTab->setDocumentMode(true); #endif setCentralWidget(DocumentTab); connect(DocumentTab, SIGNAL(currentChanged(int)), SLOT(slotChangeView())); // Give every tab a close button, and connect the button's signal to // slotFileClose DocumentTab->setTabsClosable(true); connect(DocumentTab, SIGNAL(tabCloseRequested(int)), SLOT(slotFileClose(int))); #ifdef HAVE_QTABWIDGET_SETMOVABLE // make tabs draggable if supported DocumentTab->setMovable (true); #endif dock = new QDockWidget(tr("Main Dock"),this); TabView = new QTabWidget(dock); TabView->setTabPosition(QTabWidget::West); #if __APPLE__ TabView->setDocumentMode(true); #endif connect(dock, SIGNAL(visibilityChanged(bool)), SLOT(slotToggleDock(bool))); view = new MouseActions(this); editText = new QLineEdit(this); // for editing component properties editText->setFrame(false); editText->setHidden(true); QPalette p = palette(); p.setColor(backgroundRole(), QucsSettings.BGColor); editText->setPalette(p); connect(editText, SIGNAL(returnPressed()), SLOT(slotApplyCompText())); connect(editText, SIGNAL(textChanged(const QString&)), SLOT(slotResizePropEdit(const QString&))); connect(editText, SIGNAL(editingFinished()), SLOT(slotHideEdit())); // ---------------------------------------------------------- // "Project Tab" of the left QTabWidget QWidget *ProjGroup = new QWidget(); QVBoxLayout *ProjGroupLayout = new QVBoxLayout(); QWidget *ProjButts = new QWidget(); QPushButton *ProjNew = new QPushButton(tr("New")); connect(ProjNew, SIGNAL(clicked()), SLOT(slotButtonProjNew())); QPushButton *ProjOpen = new QPushButton(tr("Open")); connect(ProjOpen, SIGNAL(clicked()), SLOT(slotButtonProjOpen())); QPushButton *ProjDel = new QPushButton(tr("Delete")); connect(ProjDel, SIGNAL(clicked()), SLOT(slotButtonProjDel())); QHBoxLayout *ProjButtsLayout = new QHBoxLayout(); ProjButtsLayout->addWidget(ProjNew); ProjButtsLayout->addWidget(ProjOpen); ProjButtsLayout->addWidget(ProjDel); ProjButts->setLayout(ProjButtsLayout); #if __APPLE__ ProjGroupLayout->setContentsMargins(0, 0, 0, 0); ProjGroupLayout->setSpacing(0); ProjButtsLayout->setContentsMargins(5, 0, 5, 0); ProjButtsLayout->setSpacing(5); #endif ProjGroupLayout->addWidget(ProjButts); Projects = new QListView(); ProjGroupLayout->addWidget(Projects); ProjGroup->setLayout(ProjGroupLayout); TabView->addTab(ProjGroup, tr("Projects")); TabView->setTabToolTip(TabView->indexOf(ProjGroup), tr("content of project directory")); connect(Projects, SIGNAL(doubleClicked(const QModelIndex &)), this, SLOT(slotListProjOpen(const QModelIndex &))); // ---------------------------------------------------------- // "Content" Tab of the left QTabWidget Content = new ProjectView(this); Content->setContextMenuPolicy(Qt::CustomContextMenu); TabView->addTab(Content, tr("Content")); TabView->setTabToolTip(TabView->indexOf(Content), tr("content of current project")); connect(Content, SIGNAL(clicked(const QModelIndex &)), SLOT(slotSelectSubcircuit(const QModelIndex &))); connect(Content, SIGNAL(doubleClicked(const QModelIndex &)), SLOT(slotOpenContent(const QModelIndex &))); // ---------------------------------------------------------- // "Component" Tab of the left QTabWidget QWidget *CompGroup = new QWidget(); QVBoxLayout *CompGroupLayout = new QVBoxLayout(); QHBoxLayout *CompSearchLayout = new QHBoxLayout(); simulatorsCombobox = new QComboBox(this); simulatorsCombobox->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred); connect(simulatorsCombobox, SIGNAL(activated(int)), SLOT(slotChangeSimulator(int))); CompChoose = new QComboBox(this); CompComps = new QListWidget(this); CompComps->setViewMode(QListView::IconMode); CompComps->setGridSize(QSize(110,90)); CompComps->setResizeMode(QListView::Adjust); CompComps->setIconSize(QSize(64,64)); CompComps->setAcceptDrops(false); const QString itemStyle = R"( QListWidget { background: white; color: black; outline: none; padding: 0px; margin: 0px; } QListWidget::item { position: relative; margin-top: 0px; margin-left: 3px; margin-right: 3px; margin-bottom: 0px; border: none; border-radius: 2px; outline: none; background-color: transparent; } QListWidget::item:hover { background-color: rgba(220, 242, 255, 0.75); } QListWidget::item:selected { position: relative; background-color: rgba(181, 227, 255, 0.75); border: none; color: black; margin: 0px; } QListWidget::item:selected:focus { position: relative; background-color: rgba(181, 227, 255, 0.75); border: none; color: black; margin: 0px; } QListWidget::item:selected:!focus { position: relative; background-color: rgba(222, 222, 223, 0.75); border: none; color: black; margin: 0px; } QListWidget::item:selected:hover { position: relative; border: 1px solid black; /* Black border when selected and hovered */ padding-top:-1px; } QListWidget::item:focus { outline: none; /* Remove the default focus outline */ } )"; CompComps->setStyleSheet(itemStyle); // Setup component search box and button. CompSearch = new QLineEdit(this); CompSearch->setPlaceholderText(tr("Search Components")); CompSearchClear = new QPushButton(tr("Clear")); CompSearchLayout->addWidget(CompSearch); CompSearchLayout->addWidget(CompSearchClear); // Setup the component chooser widgets. CompGroupLayout->setSpacing(5); CompGroupLayout->addLayout(CompSearchLayout); CompGroupLayout->addWidget(CompChoose); CompGroupLayout->addWidget(CompComps); CompGroup->setLayout(CompGroupLayout); #if __APPLE__ CompGroupLayout->setContentsMargins(0, 0, 0, 0); CompGroupLayout->setSpacing(0); CompSearchLayout->setContentsMargins(5, 0, 5, 0); CompSearchLayout->setSpacing(5); #endif TabView->addTab(CompGroup,tr("Components")); TabView->setTabToolTip(TabView->indexOf(CompGroup), tr("components and diagrams")); connect(CompChoose, SIGNAL(activated(int)), SLOT(slotSetCompView(int))); connect(CompComps, SIGNAL(itemActivated(QListWidgetItem*)), SLOT(slotSelectComponent(QListWidgetItem*))); connect(CompComps, SIGNAL(itemPressed(QListWidgetItem*)), SLOT(slotSelectComponent(QListWidgetItem*))); connect(CompSearch, SIGNAL(textEdited(const QString &)), SLOT(slotSearchComponent(const QString &))); connect(CompSearchClear, SIGNAL(clicked()), SLOT(slotSearchClear())); // ---------------------------------------------------------- // "Libraries" Tab of the left QTabWidget QWidget *LibGroup = new QWidget (); QVBoxLayout *LibGroupLayout = new QVBoxLayout (); LibCompSearch = new QLineEdit(this); LibCompSearch->setPlaceholderText(tr("Search Lib Components")); QPushButton *LibCompSearchClear = new QPushButton(tr("Clear")); connect(LibCompSearch, SIGNAL(textEdited(const QString &)), SLOT(slotSearchLibComponent(const QString &))); connect(LibCompSearchClear, SIGNAL(clicked()), SLOT(slotSearchLibClear())); QHBoxLayout *LibCompSearchLayout = new QHBoxLayout; LibCompSearchLayout->addWidget(LibCompSearch); LibCompSearchLayout->addWidget(LibCompSearchClear); LibGroupLayout->addLayout(LibCompSearchLayout); #if __APPLE__ LibGroupLayout->setContentsMargins(0, 0, 0, 0); LibGroupLayout->setSpacing(0); LibCompSearchLayout->setContentsMargins(5, 0, 5, 0); LibCompSearchLayout->setSpacing(5); #endif libTreeWidget = new QTreeWidget (this); libTreeWidget->setColumnCount (1); QStringList headers; headers.clear (); headers << tr ("Libraries"); libTreeWidget->setHeaderLabels (headers); LibGroupLayout->addWidget (libTreeWidget,6); btnShowModel = new QPushButton(tr("Show model")); connect(btnShowModel,SIGNAL(clicked(bool)),this,SLOT(slotShowModel())); LibGroupLayout->addWidget(btnShowModel); CompDescr = new QTextEdit; CompDescr->setReadOnly(true); LibGroupLayout->addWidget(CompDescr,2); Symbol = new SymbolWidget; Symbol->disableDragNDrop(); LibGroupLayout->addWidget(Symbol); LibGroup->setLayout (LibGroupLayout); //fillLibrariesTreeView (); fillComboBox(true); slotSetCompView(0); TabView->addTab (LibGroup, tr("Libraries")); TabView->setTabToolTip (TabView->indexOf (CompGroup), tr ("system and user component libraries")); connect(libTreeWidget, SIGNAL(itemPressed (QTreeWidgetItem*, int)), SLOT(slotSelectLibComponent (QTreeWidgetItem*))); // ---------------------------------------------------------- // put the tab widget in the dock dock->setWidget(TabView); dock->setAllowedAreas(Qt::LeftDockWidgetArea); this->addDockWidget(Qt::LeftDockWidgetArea, dock); TabView->setCurrentIndex(2); #if __APPLE__ QWidgetList widgets = TabView->findChildren(); foreach(QWidget* widget, widgets) { widget->setAttribute(Qt::WA_MacShowFocusRect, false); } #endif // ---------------------------------------------------------- // Octave docking window octDock = new QDockWidget(tr("Octave Dock")); connect(octDock, SIGNAL(visibilityChanged(bool)), SLOT(slotToggleOctave(bool))); octave = new OctaveWindow(octDock); this->addDockWidget(Qt::BottomDockWidgetArea, octDock); this->setCorner(Qt::BottomLeftCorner , Qt::LeftDockWidgetArea); // ............................................ messageDock = new MessageDock(this); // initial projects directory model m_homeDirModel = new QucsFileSystemModel(this); m_proxyModel = new QucsSortFilterProxyModel(); //m_proxyModel->setDynamicSortFilter(true); // show all directories (project and non-project) m_homeDirModel->setFilter(QDir::NoDot | QDir::AllDirs); // ............................................ QString path = QucsSettings.qucsWorkspaceDir.absolutePath(); QDir ProjDir(path); // initial projects directory is the Qucs home directory QucsSettings.projsDir.setPath(path); // create home dir if not exist if(!ProjDir.exists()) { if(!ProjDir.mkdir(path)) { QMessageBox::warning(this, tr("Warning"), tr("Cannot create work directory !")); return; } } // ............................................ readProjects(); // reads all projects and inserts them into the ListBox } // Put all available libraries into ComboBox. void QucsApp::fillLibrariesTreeView () { QList topitems; libTreeWidget->clear(); // make the system libraries section header QTreeWidgetItem* newitem = new QTreeWidgetItem((QTreeWidget*)0, QStringList("System Libraries")); newitem->setChildIndicatorPolicy (QTreeWidgetItem::DontShowIndicator); QFont sectionFont = newitem->font(0); sectionFont.setItalic (true); sectionFont.setBold (true); newitem->setFont (0, sectionFont); // newitem->setBackground topitems.append (newitem); populateLibTreeFromDir(QucsSettings.LibDir, topitems, true); // make the user libraries section header newitem = new QTreeWidgetItem((QTreeWidget*)0, QStringList("User Libraries")); newitem->setChildIndicatorPolicy (QTreeWidgetItem::DontShowIndicator); newitem->setFont (0, sectionFont); topitems.append (newitem); QString UserLibDirPath = QucsSettings.qucsWorkspaceDir.canonicalPath () + "/user_lib/"; populateLibTreeFromDir(UserLibDirPath, topitems); // make the user libraries section header newitem = new QTreeWidgetItem((QTreeWidget*)0, QStringList("Project Libraries")); newitem->setChildIndicatorPolicy (QTreeWidgetItem::DontShowIndicator); newitem->setFont (0, sectionFont); topitems.append (newitem); if (!ProjName.isEmpty()) { populateLibTreeFromDir(QucsSettings.QucsWorkDir.absolutePath(), topitems, true); } libTreeWidget->insertTopLevelItems(0, topitems); } bool QucsApp::populateLibTreeFromDir(const QString &LibDirPath, QList &topitems, bool relpath) { QDir LibDir(LibDirPath); QStringList LibFiles = LibDir.entryList(QStringList("*.lib"), QDir::Files, QDir::Name); QStringList blacklist = getBlacklistedLibraries(QucsSettings.LibDir); for (const QString& ss: blacklist) { // exclude blacklisted files LibFiles.removeAll(ss); } // create top level library items, base on the library names for(auto it = LibFiles.begin(); it != LibFiles.end(); it++) { QString libPath(LibDir.absoluteFilePath(*it)); libPath.chop(4); // remove extension ComponentLibrary parsedlibrary; int result = parseComponentLibrary (libPath , parsedlibrary, QUCS_COMP_LIB_FULL, relpath); QStringList nameAndFileName; nameAndFileName.append (parsedlibrary.name); nameAndFileName.append (LibDirPath + *it); QTreeWidgetItem* newlibitem = new QTreeWidgetItem((QTreeWidget*)nullptr, nameAndFileName); switch (result) { case QUCS_COMP_LIB_IO_ERROR: { QString filename = getLibAbsPath(libPath); QMessageBox::critical(nullptr, tr ("Error"), tr("Cannot open \"%1\".").arg (filename)); return false; } case QUCS_COMP_LIB_CORRUPT: QMessageBox::critical(nullptr, tr("Error"), tr("Library is corrupt.")); return false; default: break; } for (int i = 0; i < parsedlibrary.components.count (); i++) { QStringList compNameAndDefinition; compNameAndDefinition.append (parsedlibrary.components[i].name); QString s = "\n"; s += "\n " + parsedlibrary.components[i].modelString + "\n" + "\n"; compNameAndDefinition.append (s); compNameAndDefinition.append(parsedlibrary.components[i].definition); compNameAndDefinition.append(libPath); QTreeWidgetItem* newcompitem = new QTreeWidgetItem(newlibitem, compNameAndDefinition); // Silence warning from the compiler about unused variable newcompitem // we pass the pointer to the parent item in the constructor Q_UNUSED( newcompitem ) } topitems.append (newlibitem); } return true; } // --------------------------------------------------------------- // Returns a pointer to the QucsDoc object whose number is "No". // If No < 0 then a pointer to the current document is returned. QucsDoc* QucsApp::getDoc(int No) { QWidget *w; if(No < 0) w = DocumentTab->currentWidget(); else w = DocumentTab->widget(No); if(w) { if(isTextDocument (w)) return (QucsDoc*) ((TextDoc*)w); else return (QucsDoc*) ((Schematic*)w); } return 0; } // --------------------------------------------------------------- // Returns a pointer to the QucsDoc object whose file name is "Name". QucsDoc * QucsApp::findDoc (QString File, int * Pos) { QucsDoc * d; int No = 0; File = QDir::toNativeSeparators (File); while ((d = getDoc (No++)) != 0) if (QDir::toNativeSeparators (d->getDocName()) == File) { if (Pos) *Pos = No - 1; return d; } return 0; } // --------------------------------------------------------------- // Put the component groups into the ComboBox. It is possible to // only put the paintings in it, because of "symbol painting mode". // if setAll, add all categories to combobox // if not, set just paintings (symbol painting mode) // return stored index int QucsApp::fillComboBox(bool setAll) { fillLibrariesTreeView(); // заполняем библиотеки //CompChoose->setMaxVisibleItems (13); // Increase this if you add items below. auto currentText = CompChoose->currentText(); CompChoose->clear(); CompSearch->clear(); // clear the search box, in case search was active... Module::unregisterModules(); Module::registerModules(); Module::registerDynamicComponents(); int idx = 0; if (!setAll) { CompChoose->insertItem(CompChoose->count(), QObject::tr("paintings")); } else { QStringList cats = Category::getCategories(); for (const QString &it: cats) { CompChoose->insertItem(CompChoose->count(), it); } idx = CompChoose->findText(currentText); //CompChoose->setCurrentIndex(idx == -1 ? 0 : idx); } return idx == -1 ? 0 : idx; } void QucsApp::fillSimulatorsComboBox() { simulatorsCombobox->clear(); //simulatorsCombobox->addItem(spicecompat::getDefaultSimulatorName(spicecompat::simNotSpecified), 0); if (misc::simulatorExists(QucsSettings.NgspiceExecutable)) { QucsSettings.NgspiceExecutable = misc::unwrapExePath(QucsSettings.NgspiceExecutable); simulatorsCombobox->addItem(spicecompat::getDefaultSimulatorName(spicecompat::simNgspice), 1); } if (misc::simulatorExists(QucsSettings.XyceExecutable)) { QucsSettings.XyceExecutable = misc::unwrapExePath(QucsSettings.XyceExecutable); simulatorsCombobox->addItem(spicecompat::getDefaultSimulatorName(spicecompat::simXyce), 2); } if (misc::simulatorExists(QucsSettings.SpiceOpusExecutable)) { QucsSettings.SpiceOpusExecutable = misc::unwrapExePath(QucsSettings.SpiceOpusExecutable); simulatorsCombobox->addItem(spicecompat::getDefaultSimulatorName(spicecompat::simSpiceOpus), 4); } if (misc::simulatorExists(QucsSettings.Qucsator)) { QucsSettings.Qucsator = misc::unwrapExePath(QucsSettings.Qucsator); simulatorsCombobox->addItem(spicecompat::getDefaultSimulatorName(spicecompat::simQucsator), 8); } bool anySimulatorsFound = simulatorsCombobox->count() > 0; if (anySimulatorsFound) { QString current = spicecompat::getDefaultSimulatorName(QucsSettings.DefaultSimulator); int idx = simulatorsCombobox->findText(current); idx = idx < 0 ? 0 : idx; simulatorsCombobox->setCurrentIndex(idx); QucsSettings.DefaultSimulator = simulatorsCombobox->itemData(idx).toInt(); } else { QucsSettings.DefaultSimulator = spicecompat::simNotSpecified; } simulate->setEnabled(anySimulatorsFound); simulatorsCombobox->setEnabled(anySimulatorsFound); } void QucsApp::slotChangeSimulator(int index) { int simu = spicecompat::simNotSpecified; int idx = simulatorsCombobox->itemData(index).toInt(); switch (idx) { case 1: simu = spicecompat::simNgspice; break; case 2: simu = spicecompat::simXyce; break; case 4: simu = spicecompat::simSpiceOpus; break; case 8: simu = spicecompat::simQucsator; break; default: break; } if (QucsSettings.DefaultSimulator == simu) { return; } QucsSettings.DefaultSimulator = simu; saveApplSettings(); int idx1 = fillComboBox(true); slotSetCompView(idx1); // Call update() to update subcircuit symbols in current Schematic document. // TextDoc has no viewport, it needs no update. QString tabType = DocumentTab->currentWidget()->metaObject()->className(); if (tabType == "Schematic") { ((Q3ScrollView*)DocumentTab->currentWidget())->viewport()->update(); } //SimulatorLabel->setText(spicecompat::getDefaultSimulatorName(QucsSettings.DefaultSimulator)); } // ---------------------------------------------------------- // Whenever the Component Library ComboBox is changed, this slot fills the // Component IconView with the appropriate components. void QucsApp::slotSetCompView (int index) { //qDebug() << "QucsApp::slotSetCompView(" << index << ")"; editText->setHidden (true); // disable text edit of component property QList Comps; if (CompChoose->count () <= 0) return; // was in "search mode" ? if (CompChoose->itemText(0) == tr("Search results")) { if (index == 0) // user selected "Search results" item return; CompChoose->removeItem(0); CompSearch->clear(); // clear the search box --index; // adjust requested index since item 0 was removed } while (CompComps->count() > 0) { QListWidgetItem * item = CompComps->takeItem(0); if (item != nullptr) { delete item; } } CompComps->clear (); // clear the IconView // make sure the right index is selected // (might have been called by a cleared search and not by user action) CompChoose->setCurrentIndex(index); int compIdx; QString item = CompChoose->itemText (index); int catIdx = Category::getModulesNr(item); Comps = Category::getModules(item); QString Name; pInfoFunc Infos = 0; // if something was registered dynamically, get and draw icons into dock if (item == QObject::tr("verilog-a user devices")) { compIdx = 0; QMapIterator i(Module::vaComponents); while (i.hasNext()) { i.next(); // default icon initially matches the module name //Name = i.key(); // Just need path to bitmap, do not create an object QString vaBitmap; Component * c = (Component *) vacomponent::info (Name, vaBitmap, false, i.value()); if (c) delete c; // check if icon exists, fall back to default QString iconPath = QucsSettings.QucsWorkDir.filePath(vaBitmap+".png"); QFile iconFile(iconPath); QPixmap vaIcon; if(iconFile.exists()) { // load bitmap defined on the JSON symbol file vaIcon = QPixmap(iconPath); } else { QMessageBox::information(this, tr("Info"), tr("Default icon not found:\n %1.png").arg(vaBitmap)); // default icon vaIcon = QPixmap(":/bitmaps/editdelete.png"); } QListWidgetItem *icon = new QListWidgetItem(vaIcon, Name); icon->setToolTip(Name); icon->setData(Qt::UserRole + 1, catIdx); icon->setData(Qt::UserRole + 2, compIdx); CompComps->addItem(icon); compIdx++; } } else { // static components char * File; // Populate list of component bitmaps compIdx = 0; QList::const_iterator it; for (it = Comps.constBegin(); it != Comps.constEnd(); it++) { Infos = (*it)->info; if (Infos) { /// \todo warning: expression result unused, can we rewrite this? //(void) *((*it)->info) (Name, File, false); Component* c = (Component*)Infos(Name, File, true); if (c) delete c; QString icon_path = misc::getIconPath(QString (File)); QListWidgetItem *icon = new QListWidgetItem(Name); if (QFileInfo::exists(icon_path)) { icon->setIcon(QPixmap(icon_path)); } else { icon->setIcon(*(*it)->icon); } icon->setToolTip(Name); icon->setData(Qt::UserRole + 1, catIdx); icon->setData(Qt::UserRole + 2, compIdx); CompComps->addItem(icon); } compIdx++; } } } // ------------------------------------------------------------------ // When CompSearch is being edited, create a temp page show the // search result void QucsApp::slotSearchComponent(const QString &searchText) { // qDebug() << "User search: " << searchText; CompComps->clear (); // clear the IconView // not already in "search mode" if (CompChoose->itemText(0) != tr("Search results")) { ccCurIdx = CompChoose->currentIndex(); // remember current panel // insert "Search results" at the beginning, so that it is visible CompChoose->insertItem(-1, tr("Search results")); CompChoose->setCurrentIndex(0); } if (searchText.isEmpty()) { slotSetCompView(CompChoose->currentIndex()); } else { CompChoose->setCurrentIndex(0); // make sure the "Search results" category is selected editText->setHidden (true); // disable text edit of component property //traverse all component and match searchText with name QString Name; char * File; QList Comps; QStringList cats = Category::getCategories (); int catIdx = 0; for (const QString& it : cats) { // this will go also over the "verilog-a user devices" category, if present // but since modules there have no 'info' function it won't handle them Comps = Category::getModules(it); QList::const_iterator modit; int compIdx = 0; for (modit = Comps.constBegin(); modit != Comps.constEnd(); modit++) { if ((*modit)->info) { /// \todo warning: expression result unused, can we rewrite this? (void) *((*modit)->info) (Name, File, false); if((Name.indexOf(searchText, 0, Qt::CaseInsensitive)) != -1) { //match QString icon_path = misc::getIconPath(QString (File)); QListWidgetItem *icon = new QListWidgetItem(Name); if (QFileInfo::exists(icon_path)) { icon->setIcon(QPixmap(icon_path)); } else { icon->setIcon(*(*modit)->icon); } icon->setToolTip(it + ": " + Name); // add component category and module indexes to the icon icon->setData(Qt::UserRole + 1, catIdx); icon->setData(Qt::UserRole + 2, compIdx); CompComps->addItem(icon); } } compIdx++; } catIdx++; } // the "verilog-a user devices" is the last category, if present QMapIterator i(Module::vaComponents); int compIdx = 0; while (i.hasNext()) { i.next(); // Just need path to bitmap, do not create an object QString vaName, vaBitmap; vacomponent::info (vaName, vaBitmap, false, i.value()); if((vaName.indexOf(searchText, 0, Qt::CaseInsensitive)) != -1) { //match // check if icon exists, fall back to default QString iconPath = QucsSettings.QucsWorkDir.filePath(vaBitmap+".png"); QFile iconFile(iconPath); QPixmap vaIcon; if(iconFile.exists()) { // load bitmap defined on the JSON symbol file vaIcon = QPixmap(iconPath); } else { // default icon vaIcon = QPixmap(":/bitmaps/editdelete.png"); } // Add icon an name tag to dock QListWidgetItem *icon = new QListWidgetItem(vaIcon, vaName); icon->setToolTip(tr("verilog-a user devices") + ": " + vaName); // Verilog-A is the last category icon->setData(Qt::UserRole + 1, catIdx-1); icon->setData(Qt::UserRole + 2, compIdx); CompComps->addItem(icon); } compIdx++; } } } // ------------------------------------------------------------------ void QucsApp::slotSearchClear() { // was in "search mode" ? if (CompChoose->itemText(0) == tr("Search results")) { CompChoose->removeItem(0); // remove the added "Search results" item CompSearch->clear(); // go back to the panel selected before search started slotSetCompView(ccCurIdx); // the added "Search results" panel text will be removed by slotSetCompView() } } // ------------------------------------------------------------------ // Is called when the mouse is clicked within the Component QIconView. void QucsApp::slotSelectComponent(QListWidgetItem *item) { slotHideEdit(); // disable text edit of component property // delete previously selected elements if(view->selElem != 0) delete view->selElem; view->selElem = 0; // no component/diagram/painting selected if(item == 0) { // mouse button pressed not over an item ? CompComps->clearSelection(); // deselect component in ViewList return; } //if(view->drawn) // ((Q3ScrollView*)DocumentTab->currentWidget())->viewport()->update(); //view->drawn = false; // toggle last toolbar button off if(activeAction) { activeAction->blockSignals(true); // do not call toggle slot activeAction->setChecked(false); // set last toolbar button off activeAction->blockSignals(false); } activeAction = 0; MouseMoveAction = &MouseActions::MMoveElement; MousePressAction = &MouseActions::MPressElement; MouseReleaseAction = 0; MouseDoubleClickAction = 0; pInfoFunc Infos = 0; pInfoVAFunc InfosVA = 0; int i = CompComps->row(item); QList Comps; // if symbol mode, only paintings are enabled. Comps = Category::getModules(CompChoose->currentText()); QString name = CompComps->item(i)->text(); QString CompName; QString CompFile_qstr; char *CompFile_cptr; qDebug() << "pressed CompComps id" << i << name; int catIdx = CompComps->item(i)->data(Qt::UserRole + 1).toInt(); int compIdx = CompComps->item(i)->data(Qt::UserRole + 2).toInt(); qDebug() << "slotSelectComponent()" << catIdx << compIdx; Category* cat = Category::Categories.at(catIdx); Module *mod = cat->Content.at(compIdx); qDebug() << "mod->info" << mod->info; qDebug() << "mod->infoVA" << mod->infoVA; Infos = mod->info; if (Infos) { // static component view->selElem = (*mod->info) (CompName, CompFile_cptr, true); } else { // Verilog-A component InfosVA = mod->infoVA; // get JSON file out of item name on widgetitem QString filename = Module::vaComponents[name]; if (InfosVA) { view->selElem = (*InfosVA) (CompName, CompFile_qstr, true, filename); } } // in "search mode" ? if (CompChoose->itemText(0) == tr("Search results")) { if (Infos || InfosVA) { // change currently selected category, so the user will // see where the component comes from CompChoose->setCurrentIndex(catIdx+1); // +1 due to the added "Search Results" item ccCurIdx = catIdx; // remember the category to select when exiting search //!! comment out the above two lines if you would like that the search //!! returns back to the last selected category instead } } } // #################################################################### // ##### Functions for the menu that appears when right-clicking ##### // ##### on a file in the "Content" ListView. ##### // #################################################################### void QucsApp::initCursorMenu() { ContentMenu = new QMenu(this); #define APPEND_MENU(action, slot, text) \ { \ action = new QAction(tr(text), ContentMenu); \ connect(action, SIGNAL(triggered()), SLOT(slot())); \ ContentMenu->addAction(action); \ } APPEND_MENU(ActionCMenuOpen, slotCMenuOpen, "Open") APPEND_MENU(ActionCMenuCopy, slotCMenuCopy, "Duplicate") APPEND_MENU(ActionCMenuRename, slotCMenuRename, "Rename") APPEND_MENU(ActionCMenuDelete, slotCMenuDelete, "Delete") APPEND_MENU(ActionCMenuInsert, slotCMenuInsert, "Insert") #undef APPEND_MENU connect(Content, SIGNAL(customContextMenuRequested(const QPoint&)), SLOT(slotShowContentMenu(const QPoint&))); } // ---------------------------------------------------------- // Shows the menu. void QucsApp::slotShowContentMenu(const QPoint& pos) { QModelIndex idx = Content->indexAt(pos); if (idx.isValid() && idx.parent().isValid()) { ActionCMenuInsert->setVisible( idx.sibling(idx.row(), 1).data().toString().contains(tr("-port")) ); ContentMenu->popup(Content->mapToGlobal(pos)); } } // ---------------------------------------------------------- QString QucsApp::fileType (const QString& Ext) { QString Type = tr("unknown"); if (Ext == "v") Type = tr("Verilog source"); else if (Ext == "va") Type = tr("Verilog-A source"); else if (Ext == "vhd" || Ext == "vhdl") Type = tr("VHDL source"); else if (Ext == "dat") Type = tr("data file"); else if (Ext == "dpl") Type = tr("data display"); else if (Ext == "sch") Type = tr("schematic"); else if (Ext == "sym") Type = tr("symbol"); else if (Ext == "vhdl.cfg" || Ext == "vhd.cfg") Type = tr("VHDL configuration"); else if (Ext == "cfg") Type = tr("configuration"); return Type; } void QucsApp::slotCMenuOpen() { slotOpenContent(Content->currentIndex()); } void QucsApp::slotCMenuCopy() { QModelIndex idx = Content->currentIndex(); //test the item is valid if (!idx.isValid() || !idx.parent().isValid()) { return; } QString filename = idx.sibling(idx.row(), 0).data().toString(); QDir dir(QucsSettings.QucsWorkDir); QString file(dir.filePath(filename)); QFileInfo fileinfo(file); //check changed file save int z = 0; //search if the doc is loaded QucsDoc *d = findDoc(file, &z); if (d != NULL && d->getDocChanged()) { DocumentTab->setCurrentIndex(z); int ret = QMessageBox::question(this, tr("Copying Qucs document"), tr("The document contains unsaved changes!\n") + tr("Do you want to save the changes before copying?"), QMessageBox::Ignore|QMessageBox::Save); if (ret == QMessageBox::Save) { d->save(); } } QString suffix = fileinfo.suffix(); QString base = fileinfo.completeBaseName(); if(base.isEmpty()) { base = filename; } bool exists = true; //generate unique name int i = 0; QString defaultName; while (exists) { ++i; defaultName = base + "_copy" + QString::number(i) + "." + suffix; exists = QFile::exists(dir.filePath(defaultName)); } bool ok; QString s = QInputDialog::getText(this, tr("Copy file"), tr("Enter new name:"), QLineEdit::Normal, defaultName, &ok); if(ok && !s.isEmpty()) { if (!s.endsWith(suffix)) { s += QStringLiteral(".") + suffix; } if (QFile::exists(dir.filePath(s))) { //check New Name exists QMessageBox::critical(this, tr("error"), tr("Cannot copy file to identical name: %1").arg(filename)); return; } if (!QFile::copy(dir.filePath(filename), dir.filePath(s))) { QMessageBox::critical(this, tr("Error"), tr("Cannot copy schematic: %1").arg(filename)); return; } //TODO: maybe require disable edit here // refresh the schematic file path //this->updateSchNameHash(); //this->updateSpiceNameHash(); slotUpdateTreeview(); } } void QucsApp::slotCMenuRename() { QModelIndex idx = Content->currentIndex(); //test the item is valid if (!idx.isValid() || !idx.parent().isValid()) { return; } QString filename = idx.sibling(idx.row(), 0).data().toString(); QString file(QucsSettings.QucsWorkDir.filePath(filename)); QFileInfo fileinfo(file); if (findDoc(file)) { QMessageBox::critical(this, tr("Error"), tr("Cannot rename an open file!")); return; } QString suffix = fileinfo.suffix(); QString base = fileinfo.completeBaseName(); if(base.isEmpty()) { base = filename; } bool ok; QString s = QInputDialog::getText(this, tr("Rename file"), tr("Enter new filename:"), QLineEdit::Normal, base, &ok); if(ok && !s.isEmpty()) { if (!s.endsWith(suffix)) { s += QStringLiteral(".") + suffix; } QDir dir(QucsSettings.QucsWorkDir.path()); if(!dir.rename(filename, s)) { QMessageBox::critical(this, tr("Error"), tr("Cannot rename file: %1").arg(filename)); return; } slotUpdateTreeview(); } } void QucsApp::slotCMenuDelete() { QModelIndex idx = Content->currentIndex(); //test the item is valid if (!idx.isValid() || !idx.parent().isValid()) { return; } QString filename = idx.sibling(idx.row(), 0).data().toString(); QString file(QucsSettings.QucsWorkDir.filePath(filename)); if (findDoc (file)) { QMessageBox::critical(this, tr("Error"), tr("Cannot delete an open file!")); return; } int No; No = QMessageBox::warning(this, tr("Warning"), tr("This will delete the file permanently! Continue ?"), QMessageBox::No | QMessageBox::Yes); if(No == QMessageBox::Yes) { if(!QFile::remove(file)) { QMessageBox::critical(this, tr("Error"), tr("Cannot delete file: %1").arg(filename)); return; } } slotUpdateTreeview(); } void QucsApp::slotCMenuInsert() { slotSelectSubcircuit(Content->currentIndex()); } // ################################################################ // ##### Functions that handle the project operations. ##### // ################################################################ // Checks for qucs directory and reads all existing Qucs projects. void QucsApp::readProjects() { QString path = QucsSettings.projsDir.absolutePath(); QString homepath = QucsSettings.qucsWorkspaceDir.absolutePath(); if (path == homepath) { // in Qucs Home, disallow further up in the dirs tree m_homeDirModel->setFilter(QDir::NoDotAndDotDot | QDir::AllDirs); } else { m_homeDirModel->setFilter(QDir::NoDot | QDir::AllDirs); } // set the root path QModelIndex rootModelIndex = m_homeDirModel->setRootPath(path); // assign the model to the proxy and the proxy to the view m_proxyModel->setSourceModel(m_homeDirModel); m_proxyModel->setSortCaseSensitivity(Qt::CaseInsensitive); // sort by first column (file name, only column show in the QListView) m_proxyModel->sort(0); Projects->setModel(m_proxyModel); // fix the listview on the root path of the model Projects->setRootIndex(m_proxyModel->mapFromSource(rootModelIndex)); } // ---------------------------------------------------------- // Is called, when "Create New Project" button is pressed. void QucsApp::slotButtonProjNew() { slotHideEdit(); // disable text edit of component property NewProjDialog *d = new NewProjDialog(this); if(d->exec() != QDialog::Accepted) return; QDir projDir(QucsSettings.projsDir.path()); QString name = d->ProjName->text(); bool open = d->OpenProj->isChecked(); if (!name.endsWith("_prj")) { name += "_prj"; } if(!projDir.mkdir(name)) { QMessageBox::information(this, tr("Info"), tr("Cannot create project directory !")); } if(open) { openProject(QucsSettings.projsDir.filePath(name)); } } // ---------------------------------------------------------- // Opens an existing project. void QucsApp::openProject(const QString& Path) { slotHideEdit(); // disable text edit of component property QDir ProjDir(QDir::cleanPath(Path)); // the full path QString openProjName = ProjDir.dirName(); // only the project directory name if(!ProjDir.exists() || !ProjDir.isReadable()) { // check project directory QMessageBox::critical(this, tr("Error"), tr("Cannot access project directory: %1").arg(Path)); return; } if (!openProjName.endsWith("_prj")) { // should not happen QMessageBox::critical(this, tr("Error"), tr("Project directory name does not end in '_prj'(%1)").arg(openProjName)); return; } if(!closeAllFiles()) return; // close files and ask for saving them Schematic *d = new Schematic(this, ""); int i = addDocumentTab(d); DocumentTab->setCurrentIndex(i); slotResetWarnings(); QucsSettings.QucsWorkDir.setPath(ProjDir.path()); octave->adjustDirectory(); Content->setProjPath(QucsSettings.QucsWorkDir.absolutePath()); TabView->setCurrentIndex(1); // switch to "Content"-Tab openProjName.chop(4); // remove "_prj" from name ProjName = openProjName; // remember the name of project QDir parentDir = QucsSettings.QucsWorkDir; parentDir.cdUp(); // show name in title of main window setWindowTitle( tr("Project: ") + ProjName + " (" + parentDir.absolutePath() + ") - " + windowTitle); fillLibrariesTreeView(); } // ---------------------------------------------------------- // Is called when the open project menu is called. void QucsApp::slotMenuProjOpen() { QString d = QFileDialog::getExistingDirectory( this, tr("Choose Project Directory for Opening"), QucsSettings.qucsWorkspaceDir.path(), QFileDialog::ShowDirsOnly | QFileDialog::DontResolveSymlinks); if(d.isEmpty()) return; openProject(d); } // ---------------------------------------------------------- // Is called, when "Open Project" button is pressed. void QucsApp::slotButtonProjOpen() { slotHideEdit(); QModelIndex idx = Projects->currentIndex(); if (!idx.isValid()) { QMessageBox::information(this, tr("Info"), tr("No project is selected !")); } else { slotListProjOpen(idx); } } // ---------------------------------------------------------- // Is called when project is double-clicked to open it. void QucsApp::slotListProjOpen(const QModelIndex &idx) { QString dName = idx.data().toString(); if (dName.endsWith("_prj")) { // it's a Qucs project openProject(QucsSettings.projsDir.filePath(dName)); } else { // it's a normal directory // change projects directory to the selected one QucsSettings.projsDir.setPath(QucsSettings.projsDir.filePath(dName)); readProjects(); //repaint(); } } // ---------------------------------------------------------- // Is called when the close project menu is called. void QucsApp::slotMenuProjClose() { slotHideEdit(); // disable text edit of component property if(!closeAllFiles()) return; // close files and ask for saving them Schematic *d = new Schematic(this, ""); int i = addDocumentTab(d); DocumentTab->setCurrentIndex(i); slotResetWarnings(); setWindowTitle(windowTitle); QucsSettings.QucsWorkDir.setPath(QucsSettings.qucsWorkspaceDir.absolutePath()); octave->adjustDirectory(); Content->setProjPath(""); TabView->setCurrentIndex(0); // switch to "Projects"-Tab ProjName = ""; fillLibrariesTreeView(); } // remove a directory recursively bool QucsApp::recurRemove(const QString &Path) { QDir projDir = QDir(Path); return projDir.removeRecursively(); } // ---------------------------------------------------------- bool QucsApp::deleteProject(const QString& Path) { slotHideEdit(); if(Path.isEmpty()) return false; QString delProjName = QDir(Path).dirName(); // only project directory name if (!delProjName.endsWith("_prj")) { // should not happen QMessageBox::critical(this, tr("Error"), tr("Project directory name does not end in '_prj' (%1)").arg(delProjName)); return false; } delProjName.chop(4); // remove "_prj" from name if(delProjName == ProjName) { QMessageBox::information(this, tr("Info"), tr("Cannot delete an open project !")); return false; } // first ask, if really delete project ? if(QMessageBox::warning(this, tr("Warning"), tr("This will destroy all the project files permanently ! Continue ?"), QMessageBox::Yes|QMessageBox::No) == QMessageBox::No) return false; if (!recurRemove(Path)) { QMessageBox::information(this, tr("Info"), tr("Cannot remove project directory!")); return false; } return true; } // ---------------------------------------------------------- // Is called, when "Delete Project" menu is activated. void QucsApp::slotMenuProjDel() { QString d = QFileDialog::getExistingDirectory( this, tr("Choose Project Directory for Deleting"), QucsSettings.qucsWorkspaceDir.path(), QFileDialog::ShowDirsOnly | QFileDialog::DontResolveSymlinks); deleteProject(d); } // ---------------------------------------------------------- // Is called, when "Delete Project" button is pressed. void QucsApp::slotButtonProjDel() { QModelIndex idx = Projects->currentIndex(); if(!idx.isValid()) { QMessageBox::information(this, tr("Info"), tr("No project is selected!")); return; } deleteProject(QucsSettings.projsDir.filePath(idx.data().toString())); } // ################################################################ // ##### Functions that handle the file operations for the ##### // ##### documents. ##### // ################################################################ void QucsApp::slotFileNew() { statusBar()->showMessage(tr("Creating new schematic...")); slotHideEdit(); // disable text edit of component property Schematic *d = new Schematic(this, ""); int i = addDocumentTab(d); DocumentTab->setCurrentIndex(i); statusBar()->showMessage(tr("Ready.")); } void QucsApp::slotSymbolNew() { statusBar()->showMessage(tr("Creating new schematic...")); slotHideEdit(); // disable text edit of component property Schematic *d = new Schematic(this, ""); int i = addDocumentTab(d); DocumentTab->setCurrentIndex(i); slotSymbolEdit(); d->setIsSymbolOnly(true); statusBar()->showMessage(tr("Ready.")); } // -------------------------------------------------------------- void QucsApp::slotTextNew() { statusBar()->showMessage(tr("Creating new text editor...")); slotHideEdit(); // disable text edit of component property TextDoc *d = new TextDoc(this, ""); int i = addDocumentTab(d); DocumentTab->setCurrentIndex(i); statusBar()->showMessage(tr("Ready.")); } // -------------------------------------------------------------- // Changes to the document "Name". If already open then it goes to it // directly, otherwise it loads it. bool QucsApp::gotoPage(const QString& Name) { int No = DocumentTab->currentIndex(); int i = 0; QucsDoc * d = findDoc (Name, &i); // search, if page is already loaded if(d) { // open page found ? d->becomeCurrent(true); DocumentTab->setCurrentIndex(i); // make new document the current return true; } QFileInfo Info(Name); bool is_sch = false; if(Info.suffix() == "sch" || Info.suffix() == "dpl" || Info.suffix() == "sym") { d = new Schematic(this, Name); i = addDocumentTab((Schematic *)d, Info.fileName()); is_sch = true; } else { d = new TextDoc(this, Name); i = addDocumentTab((TextDoc *)d, Info.fileName()); } DocumentTab->setCurrentIndex(i); if (!Info.isWritable()) { QMessageBox::warning(this,tr("Open file"), tr("Document opened in read-only mode! " "Simulation will not work. Please copy the document " "to the directory where you have write permission!")); statusBar()->showMessage("Read only"); } if(!d->load()) { // load document if possible delete d; DocumentTab->setCurrentIndex(No); return false; } slotChangeView(); if (Info.suffix() == "sym") { // We dealing with a file containing *only* a symbol definition. // Because of that we want to switch straight to symbol editing mode // and skip any actions performed with a usual schematic. Schematic *sch = (Schematic *)d; slotSymbolEdit(); sch->setIsSymbolOnly(true); } else if (is_sch) { Schematic *sch = (Schematic *)d; if (sch->checkDplAndDatNames()) sch->setChanged(true,true); } // if only an untitled document was open -> close it if(getDoc(0)->getDocName().isEmpty()) if(!getDoc(0)->getDocChanged()) delete DocumentTab->widget(0); return true; } QString lastDirOpenSave; // to remember last directory and file // -------------------------------------------------------------- void QucsApp::slotFileOpen() { slotHideEdit(); // disable text edit of component property statusBar()->showMessage(tr("Opening file...")); QString s = QFileDialog::getOpenFileName(this, tr("Enter a Schematic Name"), lastDirOpenSave.isEmpty() ? QString(QDir::homePath()) : lastDirOpenSave, QucsFileFilter); if(s.isEmpty()) statusBar()->showMessage(tr("Opening aborted"), 2000); else { updateRecentFilesList(s); gotoPage(s); lastDirOpenSave = s; // remember last directory and file statusBar()->showMessage(tr("Ready.")); } } // -------------------------------------------------------------- bool QucsApp::saveFile(QucsDoc *Doc) { if(!Doc) Doc = getDoc(); if(Doc->getDocName().isEmpty()) return saveAs(); int Result = Doc->save(); if(Result < 0) return false; // It's assumed that *.sym files contain *only* a symbol // definition. We don't want these files to be subject // of any activities or "optimizations" which may change // the symbol. if (!Doc->getDocName().endsWith(".sym")) { updatePortNumber(Doc, Result); } slotUpdateTreeview(); return true; } // -------------------------------------------------------------- void QucsApp::slotFileSave() { statusBar()->showMessage(tr("Saving file...")); DocumentTab->blockSignals(true); // no user interaction during that time slotHideEdit(); // disable text edit of component property if(!saveFile()) { DocumentTab->blockSignals(false); statusBar()->showMessage(tr("Saving aborted"), 2000); statusBar()->showMessage(tr("Ready.")); return; } DocumentTab->blockSignals(false); statusBar()->showMessage(tr("Ready.")); if(!ProjName.isEmpty()) slotUpdateTreeview(); } // -------------------------------------------------------------- bool QucsApp::saveAs() { QWidget *w = DocumentTab->currentWidget(); QucsDoc *Doc = getDoc(); int n = -1; QString s, Filter, selfilter; QStringList Filters; QFileInfo Info; while(true) { QString file_ext; s = Doc->getDocName(); Info.setFile(s); if(s.isEmpty()) { // which is default directory ? if(ProjName.isEmpty()) { if(lastDirOpenSave.isEmpty()) s = QDir::homePath(); else s = lastDirOpenSave; } else s = QucsSettings.QucsWorkDir.path(); } else { file_ext = QString("*.") + Info.suffix(); } // list of known file extensions QString ext = "vhdl;vhd;v;va;sch;dpl;m;oct;net;qnet;ckt;cir;sp;txt;sym"; QStringList extlist = ext.split (';'); if(isTextDocument (w)) { Filters << tr("VHDL Sources")+" (*.vhdl *.vhd);;" << tr("Verilog Sources")+" (*.v);;" << tr("Verilog-A Sources")+" (*.va);;" << tr("Octave Scripts")+" (*.m *.oct);;" << tr("Qucs Netlist")+" (*.net *.qnet);;" << tr("SPICE Netlist")+" (*.ckt *.cir *.sp);;" << tr("Plain Text")+" (*.txt);;" << tr("Any File")+" (*)"; Filter = Filters.join(""); bool found = false; for (const auto &ss: Filters) { if (ss.contains(file_ext)) { found = true; selfilter = ss; selfilter.chop(2); break; } } if (!found) { selfilter = Filters.first(); } } else { Schematic *sch = (Schematic *) Doc; if (sch->getIsSymbolOnly()) { Filter = tr("Subcircuit symbol") + "(*.sym)"; selfilter = tr("Subcircuit symbol") + "(*.sym)"; } else { Filter = QucsFileFilter; selfilter = tr("Schematic") + " (*.sch)"; } } s = QFileDialog::getSaveFileName(this, tr("Enter a Document Name"), s, Filter, &selfilter); if(s.isEmpty()) return false; Info.setFile(s); // try to guess the best extension ... ext = Info.suffix(); if(ext.isEmpty() || !extlist.contains(ext)) { // if no extension was specified or is unknown if (!isTextDocument (w)) { // assume it is a schematic s += ".sch"; } } Info.setFile(s); if(QFile::exists(s)) { n = QMessageBox::warning(this, tr("Warning"), tr("The file '")+Info.fileName()+tr("' already exists!\n")+ tr("Saving will overwrite the old one! Continue?"), QMessageBox::Yes|QMessageBox::No|QMessageBox::Cancel,QMessageBox::Cancel); if(n == QMessageBox::Cancel) return false; // cancel if(n == QMessageBox::No) continue; } // search, if document is open QucsDoc * d = findDoc (s); if(d) { QMessageBox::information(this, tr("Info"), tr("Cannot overwrite an open document")); return false; } break; } Doc->setName(s); DocumentTab->setTabText(DocumentTab->indexOf(w), misc::properFileName(s)); lastDirOpenSave = Info.absolutePath(); // remember last directory and file n = Doc->save(); // SAVE if(n < 0) return false; // It's assumed that *.sym files contain *only* a symbol // definition. We don't want these files to be subject // of any activities or "optimizations" which may change // the symbol. if (!Doc->getDocName().endsWith(".sym")) { updatePortNumber(Doc, n); } slotUpdateTreeview(); updateRecentFilesList(s); return true; } // -------------------------------------------------------------- void QucsApp::slotFileSaveAs() { statusBar()->showMessage(tr("Saving file under new filename...")); DocumentTab->blockSignals(true); // no user interaction during the time slotHideEdit(); // disable text edit of component property if(!saveAs()) { DocumentTab->blockSignals(false); statusBar()->showMessage(tr("Saving aborted"), 3000); statusBar()->showMessage(tr("Ready.")); return; } DocumentTab->blockSignals(false); statusBar()->showMessage(tr("Ready.")); // refresh the schematic file path slotRefreshSchPath(); if(!ProjName.isEmpty()) slotUpdateTreeview(); } // -------------------------------------------------------------- void QucsApp::slotFileSaveAll() { statusBar()->showMessage(tr("Saving all files...")); slotHideEdit(); // disable text edit of component property DocumentTab->blockSignals(true); // no user interaction during the time int No=0; QucsDoc *Doc; // search, if page is already loaded while((Doc=getDoc(No++)) != 0) { if(Doc->getDocName().isEmpty()) // make document the current ? DocumentTab->setCurrentIndex(No-1); if (saveFile(Doc)) { // Hack! TODO: Maybe it's better to let slotFileChanged() setDocumentTabChanged(No-1, false); // know about Tab number? } } DocumentTab->blockSignals(false); // Call update() to update subcircuit symbols in current Schematic document. // TextDoc has no viewport, it needs no update. QString tabType = DocumentTab->currentWidget()->metaObject()->className(); if (tabType == "Schematic") { ((Q3ScrollView*)DocumentTab->currentWidget())->viewport()->update(); } statusBar()->showMessage(tr("Ready.")); // refresh the schematic file path slotRefreshSchPath(); slotUpdateTreeview(); } // -------------------------------------------------------------- // Close the currently active file tab void QucsApp::slotFileClose() { // Using file index -1 closes the currently active file window closeFile(-1); } // -------------------------------------------------------------- // Close the file tab specified by its index void QucsApp::slotFileClose(int index) { // Call closeFile with a specific tab index closeFile(index); // Reset Tunerdialog if (TuningMode) tunerDia->slotResetTunerDialog(); } // -------------------------------------------------------------- // Common function to close a file tab specified by its index // checking for changes in the file before doing so. If called // index == -1, the active document will be closed void QucsApp::closeFile(int index) { statusBar()->showMessage(tr("Closing file...")); slotHideEdit(); // disable text edit of component property QucsDoc *Doc = getDoc(index); if(Doc->getDocChanged()) { switch(QMessageBox::warning(this,tr("Closing Qucs document"), tr("The document contains unsaved changes!\n")+ tr("Do you want to save the changes before closing?"), QMessageBox::Save|QMessageBox::Discard|QMessageBox::Cancel)) { case QMessageBox::Save : slotFileSave(); break; case QMessageBox::Cancel : return; default: break; } } DocumentTab->removeTab(index); delete Doc; if(DocumentTab->count() < 1) { // if no document left, create an untitled Schematic *d = new Schematic(this, ""); addDocumentTab(d); DocumentTab->setCurrentIndex(0); } statusBar()->showMessage(tr("Ready.")); } // -------------------------------------------------------------- bool QucsApp::closeAllFiles() { SaveDialog *sd = new SaveDialog(this); sd->setApp(this); for(int i=0; i < DocumentTab->count(); ++i) { QucsDoc *doc = getDoc(i); if(doc->getDocChanged()) sd->addUnsavedDoc(doc); } int Result = SaveDialog::DontSave; if(!sd->isEmpty()) Result = sd->exec(); delete sd; if(Result == SaveDialog::AbortClosing) return false; QucsDoc *doc = 0; while((doc = getDoc()) != 0) delete doc; //switchEditMode(true); // set schematic edit mode return true; } void QucsApp::slotFileExamples() { statusBar()->showMessage(tr("Open example…")); auto exampleFile = QFileDialog::getOpenFileName(this, tr("Select example schematic"), QucsSettings.ExamplesDir, QucsFileFilter); if (exampleFile.isEmpty()) { statusBar()->showMessage(tr("Open example canceled"), 2000); return; } gotoPage(exampleFile); statusBar()->showMessage(tr("Ready.")); } void QucsApp::slotHelpTutorial() { // pass the QUrl representation of a local file QUrl url = QUrl::fromLocalFile(QDir::cleanPath(QucsSettings.DocDir + "/tutorial/" + QObject::sender()->objectName())); QDesktopServices::openUrl(url); } void QucsApp::slotHelpTechnical() { // pass the QUrl representation of a local file QUrl url = QUrl::fromLocalFile(QDir::cleanPath(QucsSettings.DocDir + "/technical/" + QObject::sender()->objectName())); QDesktopServices::openUrl(url); } void QucsApp::slotHelpReport() { // pass the QUrl representation of a local file QUrl url = QUrl::fromLocalFile(QDir::cleanPath(QucsSettings.DocDir + "/report/" + QObject::sender()->objectName())); QDesktopServices::openUrl(url); } // -------------------------------------------------------------- // Is called when another document is selected via the TabBar. void QucsApp::slotChangeView() { QWidget *w = DocumentTab->currentWidget(); editText->setHidden (true); // disable text edit of component property QucsDoc * Doc; if(w==NULL)return; // for text documents if (isTextDocument (w)) { TextDoc *d = (TextDoc*)w; Doc = (QucsDoc*)d; // update menu entries, etc. if necessary magAll->setDisabled(true); magSel->setDisabled(true); if(cursorLeft->isEnabled()) switchSchematicDoc (false); } // for schematic documents else { Schematic *d = (Schematic*)w; Doc = (QucsDoc*)d; magAll->setDisabled(false); magSel->setDisabled(false); // already in schematic? if(cursorLeft->isEnabled()) { // which mode: schematic or symbol editor ? if((CompChoose->count() > 1) == d->getSymbolMode()) changeSchematicSymbolMode (d); } else { switchSchematicDoc(true); changeSchematicSymbolMode(d); } showGrid->setChecked(d->getGridOn()); } Doc->becomeCurrent(true); // TODO proper window title // QFileInfo Info (Doc-> getDocName()); // // if (!ProjName.isEmpty()) { // QDir parentDir = QucsSettings.QucsWorkDir; // parentDir.cdUp(); // setWindowTitle(tr("Project: ") + ProjName + " (" + parentDir.absolutePath() + ") " // + windowTitle); // } else { // setWindowTitle(Info.fileName() + " (" + Info.filePath() +") - " + windowTitle); // } HierarchyHistory.clear(); popH->setEnabled(false); } // -------------------------------------------------------------- void QucsApp::slotFileSettings () { editText->setHidden (true); // disable text edit of component property QWidget * w = DocumentTab->currentWidget (); if (isTextDocument (w)) { QucsDoc * Doc = (QucsDoc *) ((TextDoc *) w); QString ext = Doc->fileSuffix (); // Octave properties if (ext == "m" || ext == "oct") { } // Verilog-A properties else if (ext == "va") { VASettingsDialog * d = new VASettingsDialog ((TextDoc *) w); d->exec (); } // VHDL and Verilog-HDL properties else { DigiSettingsDialog * d = new DigiSettingsDialog ((TextDoc *) w); d->exec (); } } // schematic properties else { SettingsDialog * d = new SettingsDialog ((Schematic *) w); d->exec (); // TODO: It would be better to emit a signal to notify all subscribers // that the diagram settings have changed. showGrid->setChecked(static_cast(w)->getGridOn()); } } // -------------------------------------------------------------- void QucsApp::slotApplSettings() { slotHideEdit(); // disable text edit of component property QucsSettingsDialog *d = new QucsSettingsDialog(this); d->exec(); } // -------------------------------------------------------------- void QucsApp::slotRefreshSchPath() { //this->updateSchNameHash(); //this->updateSpiceNameHash(); statusBar()->showMessage(tr("The schematic search path has been refreshed."), 2000); } // -------------------------------------------------------------- void QucsApp::updatePortNumber(QucsDoc *currDoc, int No) { if(No<0) return; QString pathName = currDoc->getDocName(); QString ext = currDoc->fileSuffix (); QFileInfo Info (pathName); QString Model, File, Name = Info.fileName(); if (ext == "sch") { Model = "Sub"; } else if (ext == "vhdl" || ext == "vhd") { Model = "VHDL"; } else if (ext == "v") { Model = "Verilog"; } // update all occurencies of subcircuit in all open documents No = 0; QWidget *w; Component *pc_tmp; while((w=DocumentTab->widget(No++)) != 0) { if(isTextDocument (w)) continue; // start from the last to omit re-appended components Schematic *Doc = (Schematic*)w; for(Component *pc=Doc->a_Components->last(); pc!=0; ) { if(pc->Model == Model) { File = pc->Props.front()->Value; if((File == pathName) || (File == Name)) { pc_tmp = Doc->a_Components->prev(); Doc->recreateComponent(pc); // delete and re-append component if(!pc_tmp) break; Doc->a_Components->findRef(pc_tmp); pc = Doc->a_Components->current(); continue; } } pc = Doc->a_Components->prev(); } } } // -------------------------------------------------------------- int QucsApp::addDocumentTab(QFrame* widget, const QString& title) { int index = DocumentTab->addTab(widget, title.isEmpty() ? tr("untitled") : title); #if __APPLE__ widget->setFrameStyle(QFrame::NoFrame); QTabBar* tabBar = DocumentTab->tabBar(); QLabel* modifiedLabel = new QLabel(" ", tabBar); modifiedLabel->setFixedWidth(10); tabBar->setTabButton(index, QTabBar::RightSide, modifiedLabel); #endif return index; } // -------------------------------------------------------------- void QucsApp::setDocumentTabChanged(int index, bool changed) { #ifdef __APPLE__ ((QLabel *)DocumentTab->tabBar()->tabButton(index, QTabBar::RightSide))->setText(changed ? "\u26AB" : " "); #else if (changed) { DocumentTab->setTabIcon(index,QIcon(":bitmaps/svg/filesave.svg")); } else { DocumentTab->setTabIcon(index,QIcon()); } #endif } // -------------------------------------------------------------- // printCurrentDocument: call printerwriter to print document void QucsApp::printCurrentDocument(bool fitToPage) { statusBar()->showMessage(tr("Printing...")); slotHideEdit(); // disable text edit of component property PrinterWriter *writer = new PrinterWriter(); writer->setFitToPage(fitToPage); writer->print(DocumentTab->currentWidget()); delete writer; statusBar()->showMessage(tr("Ready.")); return; } // -------------------------------------------------------------- void QucsApp::slotFilePrint() { printCurrentDocument(false); } // -------------------------------------------------------------- // Fit printed content to page size. void QucsApp::slotFilePrintFit() { printCurrentDocument(true); } // -------------------------------------------------------------------- // Exits the application. void QucsApp::slotFileQuit() { statusBar()->showMessage(tr("Exiting application...")); slotHideEdit(); // disable text edit of component property saveSettings(); if(closeAllFiles()) { emit signalKillEmAll(); // kill all subprocesses qApp->quit(); } statusBar()->showMessage(tr("Ready.")); } //----------------------------------------------------------------- // To get all close events. void QucsApp::closeEvent(QCloseEvent* Event) { saveSettings(); if(closeAllFiles()) { emit signalKillEmAll(); // kill all subprocesses Event->accept(); qApp->quit(); } else Event->ignore(); } //----------------------------------------------------------------- // Saves settings void QucsApp::saveSettings() { saveApplSettings(); } // -------------------------------------------------------------------- // Is called when the toolbar button is pressed to go into a subcircuit. void QucsApp::slotIntoHierarchy() { slotHideEdit(); // disable text edit of component property Schematic *Doc = (Schematic*)DocumentTab->currentWidget(); Component *pc = Doc->searchSelSubcircuit(); if(pc == 0) { return; } QString s = pc->getSubcircuitFile(); if(!gotoPage(s)) { return; } HierarchyHistory.push(Doc->getDocName()); //remember for the way back popH->setEnabled(true); } // -------------------------------------------------------------------- // Is called when the toolbar button is pressed to leave a subcircuit. void QucsApp::slotPopHierarchy() { slotHideEdit(); // disable text edit of component property if(HierarchyHistory.size() == 0) return; QString Doc = HierarchyHistory.pop(); if(!gotoPage(Doc)) { HierarchyHistory.push(Doc); return; } if(HierarchyHistory.size() == 0) { popH->setEnabled(false); } } // -------------------------------------------------------------- void QucsApp::slotShowAll() { slotHideEdit(); // disable text edit of component property getDoc()->showAll(); } // -------------------------------------------------------------- void QucsApp::slotZoomToSelection() { slotHideEdit(); // disable text edit of component property getDoc()->zoomToSelection(); } // ----------------------------------------------------------- // Sets the scale factor to 1. void QucsApp::slotShowOne() { slotHideEdit(); // disable text edit of component property getDoc()->showNoZoom(); } // ----------------------------------------------------------- void QucsApp::slotZoomOut() { slotHideEdit(); // disable text edit of component property getDoc()->zoomBy(0.5f); } /*! * \brief QucsApp::slotTune * is called when the tune toolbar button is pressed. */ void QucsApp::slotTune(bool checked) { if (checked) { QWidget *w = DocumentTab->currentWidget(); // remember from which Tab the tuner was started if (isTextDocument(w)) { //Probably digital Simulation QMessageBox::warning(this, "Not implemented", "Currently tuning is not supported for this document type", QMessageBox::Ok); tune->blockSignals(true); tune->setChecked(false); tune->blockSignals(false); return; } Schematic *d = dynamic_cast(w); assert(d); bool found = false; bool digi_found = false; bool exit = false; for(Component *pc = d->a_DocComps.first(); pc != 0; pc = d->a_DocComps.next()) { if (pc->isSimulation) { found = true; } if (pc->Type == isDigitalComponent) { digi_found = true; exit = true; } } if (!found) { QMessageBox::warning(this,tr("Error"),tr("No simulations found. Tuning not possible." " Please add at least one simulation.")); exit = true; } if (digi_found) { QMessageBox::warning(this,tr("Error"),tr("Tuning not possible for digital simulation. " "Only analog simulation supported.")); } if (d->a_Diagrams->isEmpty() && !d->getSimOpenDpl()) { QMessageBox::warning(this,tr("Error"),tr("Tuning has no effect without diagrams. " "Add at least one diagram on schematic.")); exit = true; } if (exit) { tune->blockSignals(true); tune->setChecked(false); tune->blockSignals(false); return; } // instance of tuner TuningMode = true; tunerDia = new TunerDialog(w, this);//The object can be instantiated here since when checked == false the memory will be freed // inform the Tuner Dialog when a component is deleted connect(d, SIGNAL(signalComponentDeleted(Component *)), tunerDia, SLOT(slotComponentDeleted(Component *))); slotHideEdit(); // disable text edit of component property simulateToolbar->setEnabled(false); // disable workToolbar to preserve TuneMouseAction MousePressAction = &MouseActions::MPressTune; MouseReleaseAction = 0; //While Tune is active release is not needed. This puts Press Action back to normal select tunerDia->show(); } else { this->simulateToolbar->setEnabled(true); // MouseActions are reset in closing of tunerDialog class tunerDia->close();//According to QWidget documentation (http://doc.qt.io/qt-4.8/qwidget.html#close), //the object is removed since it has the Qt::WA_DeleteOnClose flag TuningMode = false; } } QWidget *QucsApp::getSchematicWidget(QucsDoc *Doc) { QWidget *w = nullptr; QFileInfo Info(QucsSettings.QucsWorkDir.filePath(Doc->getDataDisplay())); int z = 0; QFileInfo sch_inf(Doc->getDocName()); QString sch_name = sch_inf.absolutePath() + QDir::separator() + Doc->getDataDisplay(); QucsDoc *d = findDoc(sch_name, &z); // check if schematic is already open in a Tab if (d) { // schematic already loaded // this should be the simulation schematic of this data display w = DocumentTab->widget(z); } else { // schematic not yet loaded int i = 0; int No = DocumentTab->currentIndex(); // remember current Tab if(Info.suffix() == "sch" || Info.suffix() == "dpl" || Info.suffix() == "sym") { d = new Schematic(this, Info.absoluteFilePath()); i = DocumentTab->addTab((Schematic *)d, QPixmap(":/bitmaps/empty.xpm"), Info.fileName()); } else { d = new TextDoc(this, Info.absoluteFilePath()); i = DocumentTab->addTab((TextDoc *)d, QPixmap(":/bitmaps/empty.xpm"), Info.fileName()); } DocumentTab->setCurrentIndex(i); // temporarily switch to the newly created Tab if(d->load()) { // document loaded successfully w = DocumentTab->widget(i); } else { // failed loading document // load() above has already shown a QMessageBox about not being able to load the file delete d; DocumentTab->setCurrentIndex(No); } DocumentTab->setCurrentIndex(No); } return w; } /*! * \brief QucsApp::slotSimulate * is called when the simulate toolbar button is pressed. */ void QucsApp::slotSimulate(QWidget *w) { if (w == nullptr) w = DocumentTab->currentWidget(); //Check is schematic digital bool isDigital = false; if (!isTextDocument(w)) { Schematic* schematicPtr = (Schematic*)w; isDigital = schematicPtr->isDigitalCircuit(); if (isDigital && schematicPtr->getShowBias() == 0) { QMessageBox::warning(this,tr("Simulate schematic"), tr("DC bias simulation mode is not supported " "for digital schematic!")); return; } } if (QucsSettings.DefaultSimulator!=spicecompat::simQucsator && !isDigital) { slotSimulateWithSpice(); return; } slotHideEdit(); // disable text edit of component property QucsDoc *Doc; if(isTextDocument (w)) { Doc = (QucsDoc*)((TextDoc*)w); if(Doc->getSimTime().isEmpty() && ((TextDoc*)Doc)->simulation) { DigiSettingsDialog *d = new DigiSettingsDialog((TextDoc*)Doc); if(d->exec() == QDialog::Rejected) return; } } else Doc = (QucsDoc*)((Schematic*)w); if(Doc->getDocName().isEmpty()) // if document 'untitled' ... if(!saveAs()) return; // ... save schematic before // Perhaps the document was modified from another program ? QFileInfo Info(Doc->getDocName()); QString ext = Info.suffix(); if(Doc->getLastSaved().isValid()) { if(Doc->getLastSaved() < Info.lastModified()) { int No = QMessageBox::warning(this, tr("Warning"), tr("The document was modified by another program !") + '\n' + tr("Do you want to reload or keep this version ?"), QMessageBox::Yes|QMessageBox::No); if(No == QMessageBox::Yes) Doc->load(); } } slotResetWarnings(); if(Info.suffix() == "m" || Info.suffix() == "oct") { // It is an Octave script. if(Doc->getDocChanged()) Doc->save(); slotViewOctaveDock(true); octave->runOctaveScript(Doc->getDocName()); return; } if (ext == "dpl") { // simulation started from Data Display: open referenced schematic w = getSchematicWidget(Doc); } SimMessage *sim = new SimMessage(w, this); sim->setDocWidget(w); // disconnect is automatically performed, if one of the involved objects // is destroyed ! connect(sim, SIGNAL(SimulationEnded(int, SimMessage*)), this, SLOT(slotAfterSimulation(int, SimMessage*))); connect(sim, SIGNAL(displayDataPage(QString&, QString&)), this, SLOT(slotChangePage(QString&, QString&))); if (TuningMode == true) { connect(sim, SIGNAL(progressBarChanged(int)), tunerDia, SLOT(slotUpdateProgressBar(int))); } else { //It doesn't make sense to connect the slot outside the tuning mode sim->show(); } if(!sim->startProcess()) { if (TuningMode == true) sim->show();//The message window is hidden when the tuning mode is active, but in case of error such window pops up return; } // to kill it before qucs ends connect(this, SIGNAL(signalKillEmAll()), sim, SLOT(slotClose())); } // ------------------------------------------------------------------------ // Is called after the simulation process terminates. void QucsApp::slotAfterSimulation(int Status, SimMessage *sim) { if(Status != 0) { // errors ocurred ? if (TuningMode) { sim->show(); tunerDia->SimulationEnded(); } return; } if(sim->ErrText->document()->lineCount() > 1) // were there warnings ? slotShowWarnings(); int i=0; QWidget *w; // search, if page is still open while((w=DocumentTab->widget(i++)) != 0) if(w == sim->DocWidget) break; if(sim->showBias == 0) { // paint dc bias into schematic ? sim->slotClose(); // close and delete simulation window if(w) { // schematic still open ? SweepDialog *Dia = new SweepDialog((Schematic*)sim->DocWidget); // silence warning about unused variable. Q_UNUSED(Dia) } } else { if(sim->SimRunScript) { // run script octave->startOctave(); octave->runOctaveScript(sim->Script); } if(sim->SimOpenDpl) { // switch to data display if(sim->DataDisplay.right(2) == ".m" || sim->DataDisplay.right(4) == ".oct") { // Is it an Octave script? octave->startOctave(); octave->runOctaveScript(sim->DataDisplay); } else slotChangePage(sim->DocName, sim->DataDisplay); //sim->slotClose(); // close and delete simulation window } else { if(w) { if(!isTextDocument (sim->DocWidget)) { int idx = Category::getModulesNr (QObject::tr("diagrams")); CompChoose->setCurrentIndex(idx); // switch to diagrams slotSetCompView (idx); // load recent simulation data (if document is still open) ((Schematic*)sim->DocWidget)->reloadGraphs(); } } } } if(!isTextDocument (sim->DocWidget)) { //((Schematic*)sim->DocWidget)->viewport()->update(); ((Schematic*)DocumentTab->currentWidget())->viewport()->update(); } // Kill the simulation process, otherwise we have 200+++ sims in the background if(TuningMode) { sim->slotClose(); tunerDia->SimulationEnded(); } } // ------------------------------------------------------------------------ void QucsApp::slotDCbias() { getDoc()->setShowBias(0); slotSimulate(); } // ------------------------------------------------------------------------ // Changes to the corresponding data display page or vice versa. void QucsApp::slotChangePage(const QString& DocName, const QString& DataDisplay) { if(DataDisplay.isEmpty()) return; QFileInfo Info(DocName); QString Name = Info.path() + QDir::separator() + DataDisplay; QWidget *w = DocumentTab->currentWidget(); int z = 0; // search, if page is already loaded QucsDoc * d = findDoc (Name, &z); if(d) DocumentTab->setCurrentIndex(z); else { // no open page found ? QString ext = QucsDoc::fileSuffix (DataDisplay); int i = 0; if (ext != "vhd" && ext != "vhdl" && ext != "v" && ext != "va" && ext != "oct" && ext != "m") { d = new Schematic(this, Name); i = addDocumentTab((Schematic *)d, DataDisplay); } else { d = new TextDoc(this, Name); i = addDocumentTab((TextDoc *)d, DataDisplay); } DocumentTab->setCurrentIndex(i); QFile file(Name); if(file.open(QIODevice::ReadOnly)) { // try to load document file.close(); if(!d->load()) { delete d; return; } } else { if(file.open(QIODevice::ReadWrite)) { // if document doesn't exist, create d->setDataDisplay(Info.fileName()); slotUpdateTreeview(); } else { QMessageBox::critical(this, tr("Error"), tr("Cannot create ")+Name); return; } file.close(); } d->becomeCurrent(true); } if(DocumentTab->currentWidget() == w) // if page not ... if(!isTextDocument (w)) ((Schematic*)w)->reloadGraphs(); // ... changes, reload here ! TabView->setCurrentIndex(2); // switch to "Component"-Tab if (Name.right(4) == ".dpl") { int i = Category::getModulesNr (QObject::tr("diagrams")); CompChoose->setCurrentIndex(i); // switch to diagrams slotSetCompView (i); } } // ------------------------------------------------------------------- // Changes to the data display of current page. void QucsApp::slotToPage() { QucsDoc *d = getDoc(); if(d->getDataDisplay().isEmpty()) { QMessageBox::critical(this, tr("Error"), tr("No page set !")); return; } if(d->getDocName().right(2) == ".m" || d->getDocName().right(4) == ".oct") slotViewOctaveDock(true); else slotChangePage(d->getDocName(), d->getDataDisplay()); } // ------------------------------------------------------------------- // Called when file name in Project View is double-clicked // or open is selected in the context menu void QucsApp::slotOpenContent(const QModelIndex &idx) { editText->setHidden(true); // disable text edit of component property //test the item is valid if (!idx.isValid()) { return; } if (!idx.parent().isValid()) { return; } QString filename = idx.sibling(idx.row(), 0).data().toString(); QString note = idx.sibling(idx.row(), 1).data().toString(); QFileInfo Info(QucsSettings.QucsWorkDir.filePath(filename)); QString extName = Info.suffix(); if (extName == "sch" || extName == "dpl" || extName == "vhdl" || extName == "vhd" || extName == "v" || extName == "va" || extName == "m" || extName == "oct") { gotoPage(Info.absoluteFilePath()); updateRecentFilesList(Info.absoluteFilePath()); slotUpdateRecentFiles(); if(note.isEmpty()) // is subcircuit ? if(extName == "sch") return; select->blockSignals(true); // switch on the 'select' action ... select->setChecked(true); select->blockSignals(false); activeAction = select; MouseMoveAction = 0; MousePressAction = &MouseActions::MPressSelect; MouseReleaseAction = &MouseActions::MReleaseSelect; MouseDoubleClickAction = &MouseActions::MDoubleClickSelect; return; } if(extName == "dat") { editFile(Info.absoluteFilePath()); // open datasets with text editor return; } // File is no Qucs file, so go through list and search a user // defined program to open it. QStringList com; QStringList::const_iterator it = QucsSettings.FileTypes.constBegin(); while(it != QucsSettings.FileTypes.constEnd()) { if(extName == (*it).section('/',0,0)) { QString progName = (*it).section('/',1,1); com = progName.split(" "); com << Info.absoluteFilePath(); QProcess *Program = new QProcess(); //Program->setCommunication(0); QProcessEnvironment env = QProcessEnvironment::systemEnvironment(); env.insert("PATH", env.value("PATH") ); Program->setProcessEnvironment(env); QString cmd = com.at(0); QStringList com_args = com; com_args.removeAt(0); Program->start(cmd, com_args); if(Program->state()!=QProcess::Running&& Program->state()!=QProcess::Starting) { QMessageBox::critical(this, tr("Error"), tr("Cannot start \"%1\"!").arg(Info.absoluteFilePath())); delete Program; } return; } it++; } // If no appropriate program was found, open as text file. editFile(Info.absoluteFilePath()); // open datasets with text editor } // --------------------------------------------------------- // Is called when the mouse is clicked within the Content QListView. void QucsApp::slotSelectSubcircuit(const QModelIndex &idx) { editText->setHidden(true); // disable text edit of component property if(!idx.isValid()) { // mouse button pressed not over an item ? Content->clearSelection(); // deselect component in ListView return; } bool isVHDL = false; bool isVerilog = false; QModelIndex parentIdx = idx.parent(); if(!parentIdx.isValid()) { return; } QString category = parentIdx.data().toString(); if(category == tr("Schematics")) { if(idx.sibling(idx.row(), 1).data().toString().isEmpty()) return; // return, if not a subcircuit } else if(category == tr("VHDL")) isVHDL = true; else if(category == tr("Verilog")) isVerilog = true; else return; QString filename = idx.sibling(idx.row(), 0).data().toString(); QString note = idx.sibling(idx.row(), 1).data().toString(); int idx_pag = DocumentTab->currentIndex(); QString tab_titl = ""; if (idx_pag>=0) tab_titl = DocumentTab->tabText(idx_pag); if (filename == tab_titl ) return; // Forbid to paste subcircuit into itself. // delete previously selected elements if(view->selElem != 0) delete view->selElem; view->selElem = 0; // toggle last toolbar button off if(activeAction) { activeAction->blockSignals(true); // do not call toggle slot activeAction->setChecked(false); // set last toolbar button off activeAction->blockSignals(false); } activeAction = 0; Component *Comp; if(isVHDL) Comp = new VHDL_File(); else if(isVerilog) Comp = new Verilog_File(); else Comp = new Subcircuit(); Comp->Props.first()->Value = idx.sibling(idx.row(), 0).data().toString(); Comp->recreate(0); view->selElem = Comp; MouseMoveAction = &MouseActions::MMoveElement; MousePressAction = &MouseActions::MPressElement; MouseReleaseAction = 0; MouseDoubleClickAction = 0; } // --------------------------------------------------------- // Is called when the mouse is clicked within the Content QListView. void QucsApp::slotSelectLibComponent(QTreeWidgetItem *item) { // get the current document Schematic *Doc = (Schematic*)DocumentTab->currentWidget(); // if the current document is a schematic activate the paste if(!isTextDocument(Doc)) { // if there's not a higher level item, this is a top level item, // not a component item so return if(item->parent() == 0) return; if(item->text(1).isEmpty()) return; // return, if not a subcircuit // copy the subcircuit schematic to the clipboard QClipboard *cb = QApplication::clipboard(); QString model = item->text(1); QString name = item->text(0); QString list = item->text(2); QString lib = item->text(3); cb->setText(item->text(1)); qDebug()<clear(); CompDescr->setText("Name: " + name); CompDescr->append("Library: " + lib); CompDescr->append("----------------------------"); if(!getSection("Description", list, content)) { QMessageBox::critical(this, tr("Error"), tr("Library is corrupt.")); return; } CompDescr->append(content); CompDescr->moveCursor(QTextCursor::Start); if(getSection("Model", list, content)) { Symbol->ModelString = content; } if(getSection("Spice", list, content)) { Symbol->SpiceString = content; } if(getSection("VHDLModel", list, content)) { Symbol->VHDLModelString = content; } if(getSection("VerilogModel", list, content)) { Symbol->VerilogModelString = content; } if(!getSection("Symbol", list, content)) { QMessageBox::critical(this, tr("Error"), tr("Library is corrupt.")); return; } Symbol->setSymbol(content,lib,name); // activate the paste command slotEditPaste (true); } } // --------------------------------------------------------- // This function is called if the document type changes, i.e. // from schematic to text document or vice versa. void QucsApp::switchSchematicDoc (bool SchematicMode) { // switch our scroll key actions on/off according to SchematicMode cursorLeft->setEnabled(SchematicMode); cursorRight->setEnabled(SchematicMode); cursorUp->setEnabled(SchematicMode); cursorDown->setEnabled(SchematicMode); // text document if (!SchematicMode) { if (activeAction) { activeAction->blockSignals (true); // do not call toggle slot activeAction->setChecked(false); // set last toolbar button off activeAction->blockSignals (false); } activeAction = select; select->blockSignals (true); select->setChecked(true); select->blockSignals (false); } // schematic document else { MouseMoveAction = 0; MousePressAction = &MouseActions::MPressSelect; MouseReleaseAction = &MouseActions::MReleaseSelect; MouseDoubleClickAction = &MouseActions::MDoubleClickSelect; } selectMarker->setEnabled (SchematicMode); alignTop->setEnabled (SchematicMode); alignBottom->setEnabled (SchematicMode); alignLeft->setEnabled (SchematicMode); alignRight->setEnabled (SchematicMode); centerHor->setEnabled (SchematicMode); centerVert->setEnabled (SchematicMode); distrHor->setEnabled (SchematicMode); distrVert->setEnabled (SchematicMode); onGrid->setEnabled (SchematicMode); moveText->setEnabled (SchematicMode); filePrintFit->setEnabled (SchematicMode); editRotate->setEnabled (SchematicMode); editMirror->setEnabled (SchematicMode); editMirrorY->setEnabled (SchematicMode); intoH->setEnabled (SchematicMode); popH->setEnabled (SchematicMode); dcbias->setEnabled (SchematicMode); insWire->setEnabled (SchematicMode); insLabel->setEnabled (SchematicMode); insPort->setEnabled (SchematicMode); insGround->setEnabled (SchematicMode); insEquation->setEnabled (SchematicMode); setMarker->setEnabled (SchematicMode); setDiagramLimits->setEnabled (SchematicMode); exportAsImage->setEnabled (SchematicMode); // only export schematic, no text editFind->setEnabled (!SchematicMode); insEntity->setEnabled (!SchematicMode); buildModule->setEnabled(!SchematicMode); // only build if VA document } // --------------------------------------------------------- void QucsApp::switchEditMode(bool SchematicMode) { fillComboBox(SchematicMode); slotSetCompView(0); intoH->setEnabled(SchematicMode); popH->setEnabled(SchematicMode); editActivate->setEnabled(SchematicMode); changeProps->setEnabled(SchematicMode); insEquation->setEnabled(SchematicMode); insGround->setEnabled(SchematicMode); insWire->setEnabled(SchematicMode); insLabel->setEnabled(SchematicMode); setMarker->setEnabled(SchematicMode); setDiagramLimits->setEnabled(SchematicMode); selectMarker->setEnabled(SchematicMode); simulate->setEnabled(SchematicMode); // no search in "symbol painting mode" as only paintings should be used CompSearch->setEnabled(SchematicMode); CompSearchClear->setEnabled(SchematicMode); } // --------------------------------------------------------- void QucsApp::changeSchematicSymbolMode(Schematic *Doc) { if(Doc->getSymbolMode()) { // go into select modus to avoid placing a forbidden element select->setChecked(true); switchEditMode(false); } else switchEditMode(true); } // --------------------------------------------------------- bool QucsApp::isTextDocument(QWidget *w) { return w->inherits("QPlainTextEdit"); } // --------------------------------------------------------- // Is called if the "symEdit" action is activated, i.e. if the user // switches between the two painting mode: Schematic and (subcircuit) // symbol. void QucsApp::slotSymbolEdit() { QWidget *w = DocumentTab->currentWidget(); // in a text document (e.g. VHDL) if (isTextDocument (w)) { TextDoc *TDoc = (TextDoc*)w; if (!TDoc->getDocName().endsWith(".va")) { QMessageBox::warning(this,tr("Error"), tr("Symbol editing supported only for schematics and Verilog-A documents!")); return; } else { QMessageBox::warning(this,tr("Warning"), tr("Attaching symbols to Verilog-A sources is deprecated and not recommended " "for new designs. Use SPICE generic device instead. See the documentation " "for more details.")); } // set 'DataDisplay' document of text file to symbol file QFileInfo Info(TDoc->getDocName()); QString sym = Info.completeBaseName()+".sym"; TDoc->setDataDisplay(sym); // symbol file already loaded? int paint_mode = 0; if (!findDoc (QucsSettings.QucsWorkDir.filePath(sym))) paint_mode = 1; // change current page to appropriate symbol file slotChangePage(TDoc->getDocName(),TDoc->getDataDisplay()); // set 'DataDisplay' document of symbol file to original text file Schematic *SDoc = (Schematic*)DocumentTab->currentWidget(); SDoc->setDataDisplay(Info.fileName()); // change into symbol mode if (paint_mode) // but only switch coordinates if newly loaded SDoc->switchPaintMode(); SDoc->setSymbolMode(true); changeSchematicSymbolMode(SDoc); SDoc->becomeCurrent(true); SDoc->viewport()->update(); } // in a normal schematic, symbol file else { Schematic *SDoc = (Schematic*)w; if (!SDoc->getIsSymbolOnly()) { slotHideEdit(); // disable text edit of component property SDoc->switchPaintMode(); // twist the view coordinates changeSchematicSymbolMode(SDoc); SDoc->becomeCurrent(true); SDoc->viewport()->update(); } } } // ----------------------------------------------------------- void QucsApp::slotPowerMatching() { QWidget *w = DocumentTab->currentWidget(); // remember from which Tab the tuner was started if (isTextDocument(w)) return; if(!view->focusElement) return; if(view->focusElement->Type != isMarker) return; Marker *pm = (Marker*)view->focusElement; // double Z0 = 50.0; QString Var = pm->pGraph->Var; double Imag = pm->powImag(); if(Var == "Sopt") // noise matching ? Imag *= -1.0; MatchDialog *Dia = new MatchDialog(this); // Dia->Ref1Edit->setText(QString::number(Z0)); Dia->setS11LineEdits(pm->powReal(), Imag); Dia->setFrequency(pm->powFreq()); Dia->setTwoPortMatch(false); // will also cause the corresponding impedance LineEdit to be updated Schematic *sch = dynamic_cast(w); if (sch->getSimOpenDpl() || sch->getDocName().endsWith(".dpl")) { slotToPage(); } if(Dia->exec() != QDialog::Accepted) return; } // ----------------------------------------------------------- void QucsApp::slot2PortMatching() { QWidget *w = DocumentTab->currentWidget(); // remember from which Tab the tuner was started if (isTextDocument(w)) return; if(!view->focusElement) return; if(view->focusElement->Type != isMarker) return; Marker *pm = (Marker*)view->focusElement; QString DataSet; Schematic *Doc = (Schematic*)DocumentTab->currentWidget(); int z = pm->pGraph->Var.indexOf(':'); if(z <= 0) DataSet = Doc->getDataSet(); else DataSet = pm->pGraph->Var.mid(z+1); double Freq = pm->powFreq(); QFileInfo Info(Doc->getDocName()); DataSet = Info.path()+QDir::separator()+DataSet; Diagram *Diag = new Diagram(); // FIXME: use normal Diagrams. Graph *pg = new Graph(Diag, "S[1,1]"); Diag->Graphs.append(pg); if(!pg->loadDatFile(DataSet)) { QMessageBox::critical(0, tr("Error"), tr("Could not load S[1,1].")); return; } pg = new Graph(Diag, "S[1,2]"); Diag->Graphs.append(pg); if(!pg->loadDatFile(DataSet)) { QMessageBox::critical(0, tr("Error"), tr("Could not load S[1,2].")); return; } pg = new Graph(Diag, "S[2,1]"); Diag->Graphs.append(pg); if(!pg->loadDatFile(DataSet)) { QMessageBox::critical(0, tr("Error"), tr("Could not load S[2,1].")); return; } pg = new Graph(Diag, "S[2,2]"); Diag->Graphs.append(pg); if(!pg->loadDatFile(DataSet)) { QMessageBox::critical(0, tr("Error"), tr("Could not load S[2,2].")); return; } DataX const *Data = Diag->Graphs.first()->axis(0); if(Data->Var != "frequency") { QMessageBox::critical(0, tr("Error"), tr("Wrong dependency!")); return; } double *Value = Data->Points; // search for values for chosen frequency for(z=0; zcount; z++) if(*(Value++) == Freq) break; // get S-parameters double S11real = *(Diag->Graphs.at(0)->cPointsY + 2*z); double S11imag = *(Diag->Graphs.at(0)->cPointsY + 2*z + 1); double S12real = *(Diag->Graphs.at(1)->cPointsY + 2*z); double S12imag = *(Diag->Graphs.at(1)->cPointsY + 2*z + 1); double S21real = *(Diag->Graphs.at(2)->cPointsY + 2*z); double S21imag = *(Diag->Graphs.at(2)->cPointsY + 2*z + 1); double S22real = *(Diag->Graphs.at(3)->cPointsY + 2*z); double S22imag = *(Diag->Graphs.at(3)->cPointsY + 2*z + 1); delete Diag; MatchDialog *Dia = new MatchDialog(this); Dia->setTwoPortMatch(true); Dia->setFrequency(Freq); Dia->setS11LineEdits(S11real, S11imag); Dia->setS12LineEdits(S12real, S12imag); Dia->setS21LineEdits(S21real, S21imag); Dia->setS22LineEdits(S22real, S22imag); Schematic *sch = dynamic_cast(w); if (sch->getSimOpenDpl() || sch->getDocName().endsWith(".dpl")) { slotToPage(); } if(Dia->exec() != QDialog::Accepted) return; } // ----------------------------------------------------------- // Is called if the "edit" action is clicked on right mouse button menu. void QucsApp::slotEditElement() { if(view->focusMEvent) view->editElement((Schematic*)DocumentTab->currentWidget(), view->focusMEvent); } // ----------------------------------------------------------- // Hides the edit for component property. Called e.g. if QLineEdit // looses the focus. void QucsApp::slotHideEdit() { editText->setParent(this, Qt::WindowFlags()); editText->setHidden(true); } // ----------------------------------------------------------- // set document tab icon to smallsave_xpm or empty_xpm void QucsApp::slotFileChanged(bool changed) { setDocumentTabChanged(DocumentTab->currentIndex(), changed); } // ----------------------------------------------------------- // Update project view by call refresh function // looses the focus. void QucsApp::slotUpdateTreeview() { Content->refresh(); } /* // ----------------------------------------------------------- // Searches the qucs path list for all schematic files and creates // a hash for lookup later void QucsApp::updateSchNameHash(void) { // update the list of paths to search in qucsPathList, this // removes nonexisting entries updatePathList(); // now go through the paths creating a map to all the schematic files // found in the directories. Note that we go through the list of paths from // first index to last index. Since keys are unique it means schematic files // in directories at the end of the list take precedence over those at the // start of the list, we should warn about shadowing of schematic files in // this way in the future QStringList nameFilter; nameFilter << "*.sch"; // clear out any existing hash table entries schNameHash.clear(); for (const QString& qucspath : qucsPathList) { QDir thispath(qucspath); // get all the schematic files in the directory QFileInfoList schfilesList = thispath.entryInfoList( nameFilter, QDir::Files ); // put each one in the hash table with the unique key the base name of // the file, note this will overwrite the value if the key already exists for (const QFileInfo& schfile : schfilesList) { schNameHash[schfile.completeBaseName()] = schfile.absoluteFilePath(); } } // finally check the home/working directory QDir thispath(QucsSettings.QucsWorkDir); QFileInfoList schfilesList = thispath.entryInfoList( nameFilter, QDir::Files ); // put each one in the hash table with the unique key the base name of // the file, note this will overwrite the value if the key already exists for (const QFileInfo& schfile : schfilesList) { schNameHash[schfile.completeBaseName()] = schfile.absoluteFilePath(); } } */ /* // ----------------------------------------------------------- // Searches the qucs path list for all spice files and creates // a hash for lookup later void QucsApp::updateSpiceNameHash() { // update the list of paths to search in qucsPathList, this // removes nonexisting entries updatePathList(); // now go through the paths creating a map to all the spice files // found in the directories. Note that we go through the list of paths from // first index to last index. Since keys are unique it means spice files // in directories at the end of the list take precedence over those at the // start of the list, we should warn about shadowing of spice files in // this way in the future // clear out any existing hash table entries spiceNameHash.clear(); for (const QString& qucspath : qucsPathList) { QDir thispath(qucspath); // get all the schematic files in the directory QFileInfoList spicefilesList = thispath.entryInfoList( QucsSettings.spiceExtensions, QDir::Files ); // put each one in the hash table with the unique key the base name of // the file, note this will overwrite the value if the key already exists for (const QFileInfo& spicefile : spicefilesList) { schNameHash[spicefile.completeBaseName()] = spicefile.absoluteFilePath(); } } // finally check the home/working directory QDir thispath(QucsSettings.QucsWorkDir); QFileInfoList spicefilesList = thispath.entryInfoList( QucsSettings.spiceExtensions, QDir::Files ); // put each one in the hash table with the unique key the base name of // the file, note this will overwrite the value if the key already exists for (const QFileInfo& spicefile : spicefilesList) { spiceNameHash[spicefile.completeBaseName()] = spicefile.absoluteFilePath(); } } */ // ----------------------------------------------------------- // update the list of paths, pruning non-existing paths void QucsApp::updatePathList() { // check each path actually exists, if not remove it QMutableListIterator i(qucsPathList); while (i.hasNext()) { i.next(); QDir thispath(i.value()); if (!thispath.exists()) { // the path does not exist, remove it from the list i.remove(); } } } // replace the old path list with a new one void QucsApp::updatePathList(QStringList newPathList) { // clear out the old path list qucsPathList.clear(); // copy the new path into the path list for (const QString& path : newPathList) { qucsPathList.append(path); } // do the normal path update operations updatePathList(); } void QucsApp::updateRecentFilesList(QString s) { QSettings* settings = new QSettings("qucs","qucs_s"); QucsSettings.RecentDocs.removeAll(s); QucsSettings.RecentDocs.prepend(s); if (QucsSettings.RecentDocs.size() > MaxRecentFiles) { QucsSettings.RecentDocs.removeLast(); } settings->setValue("RecentDocs",QucsSettings.RecentDocs.join("*")); delete settings; slotUpdateRecentFiles(); } void QucsApp::slotSaveDiagramToGraphicsFile() { slotSaveSchematicToGraphicsFile(true); } void QucsApp::slotSaveSchematicToGraphicsFile(bool diagram) { ImageWriter *writer = new ImageWriter(lastExportFilename); writer->setDiagram(diagram); if (!writer->print(DocumentTab->currentWidget())) { lastExportFilename = writer->getLastSavedFile(); statusBar()->showMessage(QObject::tr("Successfully exported"), 2000); } delete writer; } void QucsApp::slotSimSettings() { SimSettingsDialog *SetDlg = new SimSettingsDialog(this); SetDlg->exec(); delete SetDlg; fillSimulatorsComboBox(); } void QucsApp::slotSimulateWithSpice() { if (!isTextDocument(DocumentTab->currentWidget())) { Schematic *sch = (Schematic*)DocumentTab->currentWidget(); if (TuningMode) { QFileInfo Info(sch->getDocName()); QString ext = Info.suffix(); if (ext == "dpl") { QucsDoc *Doc = (QucsDoc *)sch; sch = (Schematic *) getSchematicWidget(Doc); if (sch == nullptr) return; } } if (sch->getDocName().isEmpty()) { auto biasState = sch->getShowBias(); QMessageBox::warning(this,tr("Simulate schematic"), tr("Schematic not saved! Simulation of unsaved schematic " "not possible. Save schematic first!")); slotFileSaveAs(); sch->setShowBias(biasState); } ExternSimDialog *SimDlg = new ExternSimDialog(sch); connect(SimDlg,SIGNAL(simulated(ExternSimDialog*)), this,SLOT(slotAfterSpiceSimulation(ExternSimDialog*))); connect(SimDlg,SIGNAL(warnings()),this,SLOT(slotShowWarnings())); connect(SimDlg,SIGNAL(success()),this,SLOT(slotResetWarnings())); if (TuningMode || sch->getShowBias() == 0) SimDlg->slotStart(); else SimDlg->exec(); /*disconnect(SimDlg,SIGNAL(simulated()),this,SLOT(slotAfterSpiceSimulation())); disconnect(SimDlg,SIGNAL(warnings()),this,SLOT(slotShowWarnings())); disconnect(SimDlg,SIGNAL(success()),this,SLOT(slotResetWarnings()));*/ /*if (SimDlg->wasSimulated && sch->getSimOpenDpl()) if (sch->getShowBias() < 1) slotChangePage(sch->getDocName(),sch->getDataDisplay()); delete SimDlg;*/ } else { QMessageBox::warning(this,tr("Simulate schematic"), tr("Simulation of text document is not possible!")); } } void QucsApp::slotSaveNetlist() { if (QucsSettings.DefaultSimulator == spicecompat::simQucsator) { QMessageBox::information(this,tr("Save netlist"), tr("This action is supported only for SPICE simulators!")); return; } if (!isTextDocument(DocumentTab->currentWidget())) { Schematic *sch = (Schematic*)DocumentTab->currentWidget(); ExternSimDialog *SimDlg = new ExternSimDialog(sch, true); SimDlg->slotSaveNetlist(); delete SimDlg; } } void QucsApp::slotAfterSpiceSimulation(ExternSimDialog *SimDlg) { Schematic *sch = (Schematic*)DocumentTab->currentWidget(); disconnect(SimDlg,SIGNAL(simulated(ExternSimDialog *)), this,SLOT(slotAfterSpiceSimulation(ExternSimDialog *))); disconnect(SimDlg,SIGNAL(warnings()),this,SLOT(slotShowWarnings())); disconnect(SimDlg,SIGNAL(success()),this,SLOT(slotResetWarnings())); if (TuningMode && SimDlg->hasError()) { SimDlg->show(); return; } if (SimDlg->wasSimulated()) { if(sch->getSimOpenDpl()) { if (sch->getShowBias() < 1) { if (!TuningMode) { slotChangePage(sch->getDocName(),sch->getDataDisplay()); } else if (!sch->getDocName().endsWith(".dpl")) { slotChangePage(sch->getDocName(),sch->getDataDisplay()); } } } else { if (sch->getShowBias() < 1 && !TuningMode) { int idx = Category::getModulesNr (QObject::tr("diagrams")); CompChoose->setCurrentIndex(idx); // switch to diagrams slotSetCompView (idx); } } } sch->reloadGraphs(); sch->viewport()->update(); if(sch->getSimRunScript()) { // run script octave->startOctave(); octave->runOctaveScript(sch->getScript()); } if (TuningMode) { tunerDia->SimulationEnded(); } if (sch->getShowBias()>0 || QucsMain->TuningMode) SimDlg->close(); } void QucsApp::slotBuildVAModule() { if (!isTextDocument(DocumentTab->currentWidget())) { Schematic *Sch = (Schematic*)DocumentTab->currentWidget(); QFileInfo inf(Sch->getDocName()); QString filename = QFileDialog::getSaveFileName(this,tr("Save Verilog-A module"), inf.path()+QDir::separator()+"testmodule.va", "Verilog-A (*.va)"); if (filename.isEmpty()) return; QFile f(filename); if (f.open(QIODevice::WriteOnly)) { QTextStream stream(&f); VerilogAwriter *writer = new VerilogAwriter; if (!writer->createVA_module(stream,Sch)) { QMessageBox::critical(this,tr("Build Verilog-A module"), tr("This schematic is not a subcircuit!\n" "Use subcircuit to crete Verilog-A module!"), QMessageBox::Ok); } delete writer; f.close(); Content->refresh(); } } } /*void QucsApp::slotBuildXSPICEIfs(int mode) { if (!isTextDocument(DocumentTab->currentWidget())) { Schematic *Sch = (Schematic*)DocumentTab->currentWidget(); QFileInfo inf(Sch->getDocName()); QString msg,ext; switch(mode) { case spicecompat::cmgenSUBifs: case spicecompat::cmgenEDDifs: msg = inf.path()+QDir::separator()+inf.baseName()+".ifs"; ext = "XSPICE IFS (*.ifs)"; break; case spicecompat::cmgenSUBmod: case spicecompat::cmgenEDDmod: msg = inf.path()+QDir::separator()+inf.baseName()+".mod"; ext = "XSPICE MOD (*.mod)"; break; default: break; } QString filename = QFileDialog::getSaveFileName(this,tr("Save XSPICE source"),msg,ext); if (filename.isEmpty()) return; QFile f(filename); if (f.open(QIODevice::WriteOnly)) { QTextStream stream(&f); CodeModelGen *cmgen = new CodeModelGen; bool r = false; switch(mode) { case spicecompat::cmgenSUBifs: r = cmgen->createIFS(stream,Sch); case spicecompat::cmgenEDDifs: { for(Component *pc = Sch->a_DocComps.first(); pc != 0; pc = Sch->a_DocComps.next()) { if (pc->isSelected) { r = cmgen->createIFSfromEDD(stream,Sch,pc); break; } } } break; case spicecompat::cmgenEDDmod : { for(Component *pc = Sch->a_DocComps.first(); pc != 0; pc = Sch->a_DocComps.next()) { if (pc->isSelected) { r = cmgen->createMODfromEDD(stream,Sch,pc); break; } } } break; default: r = false; break; } QString errs; if (!r) errs = tr("Create XSPICE CodeModel" "Create CodeModel source file failed!" "Schematic is not subciruit!"); messageDock->reset(); messageDock->msgDock->setWindowTitle(tr("Debug messages dock")); messageDock->builderTabs->setTabIcon(0,QPixmap()); messageDock->builderTabs->setTabText(0,tr("XSPICE")); messageDock->builderTabs->setTabIcon(1,QPixmap()); messageDock->admsOutput-> insertPlainText(QStringLiteral("Creating XSPICE source file: %1\n").arg(filename)); errs += cmgen->getLog(); if (errs.isEmpty()) { messageDock->admsOutput->insertPlainText(tr("Success!\n")); } else { messageDock->admsOutput->insertPlainText(errs); } messageDock->msgDock->show(); delete cmgen; f.close(); } } } void QucsApp::slotEDDtoIFS() { slotBuildXSPICEIfs(spicecompat::cmgenEDDifs); } void QucsApp::slotEDDtoMOD() { slotBuildXSPICEIfs(spicecompat::cmgenEDDmod); }*/ void QucsApp::slotShowModel() { DisplayDialog *dlg = new DisplayDialog(this,Symbol->ModelString, Symbol->SpiceString); dlg->exec(); delete dlg; } void QucsApp::slotSearchLibComponent(const QString &comp) { if (comp.isEmpty()) { QTreeWidgetItemIterator top_itm(libTreeWidget); while (*top_itm) { (*top_itm)->setExpanded(false); (*top_itm)->setHidden(false); for (int i = 0; i < (*top_itm)->childCount(); i++ ) { auto itm = (*top_itm)->child(i); itm->setHidden(false); } top_itm++; } return; } QTreeWidgetItemIterator top_itm(libTreeWidget); while (*top_itm) { bool found = false; int cnt = (*top_itm)->childCount(); if (cnt < 1) { top_itm++; continue; } for (int i = 0; i < cnt; i++ ) { auto itm = (*top_itm)->child(i); QString name =itm->text(0); if (name.contains(comp,Qt::CaseInsensitive)) { itm->setHidden(false); found=true; } else { itm->setHidden(true); } } if (found) { (*top_itm)->setExpanded(true); (*top_itm)->setHidden(false); } else { (*top_itm)->setExpanded(false); (*top_itm)->setHidden(true); } top_itm++; } } void QucsApp::slotSearchLibClear() { LibCompSearch->clear(); slotSearchLibComponent(""); } QVariant QucsFileSystemModel::data( const QModelIndex& index, int role ) const { if (role == Qt::DecorationRole) { // it's an icon QString dName = fileName(index); if (dName.endsWith("_prj")) { // it's a Qucs project // for some reason SVG does not always work on Windows, so use PNG return QIcon(":bitmaps/hicolor/128x128/apps/qucs.png"); } } // return default system icon return QFileSystemModel::data(index, role); } // function below is adapted from https://stackoverflow.com/questions/10789284/qfilesystemmodel-sorting-dirsfirst bool QucsSortFilterProxyModel::lessThan(const QModelIndex &left, const QModelIndex &right) const { // if sorting by file names column if (sortColumn() == 0) { QucsFileSystemModel *model = qobject_cast(sourceModel()); // get the current sort order (do we need this ?) bool asc = sortOrder() == Qt::AscendingOrder; QFileInfo leftFileInfo = model->fileInfo(left); QFileInfo rightFileInfo = model->fileInfo(right); QString leftFileName = model->fileName(left); QString rightFileName = model->fileName(right); // If DotAndDot move in the beginning if (sourceModel()->data(left).toString() == "..") return asc; if (sourceModel()->data(right).toString() == "..") return !asc; // move dirs upper if (!leftFileInfo.isDir() && rightFileInfo.isDir()) { return !asc; } if (leftFileInfo.isDir() && !rightFileInfo.isDir()) { return asc; } // move dirs ending in '_prj' upper if (leftFileInfo.isDir() && rightFileInfo.isDir()) { if (!leftFileName.endsWith("_prj") && rightFileName.endsWith("_prj")) { return !asc; } if (leftFileName.endsWith("_prj") && !rightFileName.endsWith("_prj")) { return asc; } } } return QSortFilterProxyModel::lessThan(left, right); }