circuit-logic-simulator/MainWindow.cpp

513 lines
15 KiB
C++
Raw Normal View History

2020-03-24 20:18:39 +01:00
#include "MainWindow.h"
#include "ui_MainWindow.h"
#include <QGraphicsRectItem>
#include <QtMath>
#include <QScrollBar>
#include <QFileDialog>
#include <QMessageBox>
#include <QTimer>
#include <QCloseEvent>
#include <QDir>
#include "Connector.h"
#include "Connector.h"
#include "Wire.h"
#include "Part.h"
#include "Scene.h"
#include "Logic.h"
#include "UndoCommands/CopyParts.h"
MainWindow::MainWindow(QWidget *parent, QString loadFile)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
// Setup UI
ui->setupUi(this);
// Add tab widget to tool bar to make it moveable and floatable
// TODO: Use a less hacky solution and make it work horizontally as well
ui->toolBarBottom->addWidget(ui->tabWidget);
// Set title name
m_title = "Circuit Simulator";
// Set initial window title
setWindowTitle(m_title + " - Unsaved Circuit");
// Initialize graphics scene
m_scene = new Scene(ui->graphicsView, this);
if(!loadFile.isEmpty())
{
m_currFilename = loadFile;
m_fileLoaded = true;
if(!m_scene->m_logic->loadFromFile(loadFile))
{
m_fileLoaded = false;
}
else
setWindowTitle(m_title + " - " + QFileInfo(m_currFilename).fileName());
}
// Set active tool mode to select
toolSelect();
// Set active mode to sim started
simStart();
// Assign scene to graphics view and show it
ui->graphicsView->setScene(m_scene);
ui->graphicsView->show();
// Setup undoView
undoView = new QUndoView(m_scene->m_undoStack);
undoView->setWindowTitle("Undo View");
undoView->setAttribute(Qt::WA_QuitOnClose, false);
// Setup graphics timer
QTimer *gfxTimer = new QTimer(this);
connect(gfxTimer, &QTimer::timeout, this, &MainWindow::onGfxTimerTimeout);
gfxTimer->start(17);
// Setup logic timer
QTimer *logicTimer = new QTimer(this);
connect(logicTimer, &QTimer::timeout, this, &MainWindow::onLogicTimerTimeout);
logicTimer->start(1);
// Refresh IC list
reloadLocalICs();
//CONNECT EVENTS
connect(ui->zoomSlider, &QSlider::valueChanged, this, &MainWindow::updateMatrix);
connect(ui->btnZoomIn, &QToolButton::clicked, this, &MainWindow::zoomIn);
connect(ui->btnZoomOut, &QToolButton::clicked, this, &MainWindow::zoomOut);
connect(ui->actionZoom_In, &QAction::triggered, this, &MainWindow::zoomIn);
connect(ui->actionZoom_Out, &QAction::triggered, this, &MainWindow::zoomOut);
connect(ui->actionReset_Zoom, &QAction::triggered, this, &MainWindow::resetZoom);
connect(ui->actionToggle_Undo_View, &QAction::triggered, this, &MainWindow::toggleUndoView);
connect(ui->actionUndo, &QAction::triggered, this, &MainWindow::editUndo);
connect(ui->actionRedo, &QAction::triggered, this, &MainWindow::editRedo);
connect(ui->actionCut, &QAction::triggered, this, &MainWindow::editCut);
connect(ui->actionCopy, &QAction::triggered, this, &MainWindow::editCopy);
connect(ui->actionPaste, &QAction::triggered, this, &MainWindow::editPaste);
connect(ui->actionDelete, &QAction::triggered, this, &MainWindow::editDelete);
connect(ui->actionNew, &QAction::triggered, this, &MainWindow::fileNew);
connect(ui->actionOpen, &QAction::triggered, this, &MainWindow::fileOpen);
connect(ui->actionSave, &QAction::triggered, this, &MainWindow::fileSave);
connect(ui->actionSave_As, &QAction::triggered, this, &MainWindow::fileSaveAs);
connect(ui->actionExit, &QAction::triggered, this, &MainWindow::fileExit);
connect(ui->actionConnect, &QAction::triggered, this, &MainWindow::toolConnect);
connect(ui->actionRemove_Connections, &QAction::triggered, this, &MainWindow::toolDisconnect);
connect(ui->actionSelect, &QAction::triggered, this, &MainWindow::toolSelect);
connect(ui->actionPan, &QAction::triggered, this, &MainWindow::toolPan);
connect(ui->actionStep, &QAction::triggered, this, &MainWindow::simStep);
connect(ui->actionStart, &QAction::triggered, this, &MainWindow::simStart);
connect(ui->actionStop, &QAction::triggered, this, &MainWindow::simStop);
connect(ui->btnGateBuffer, &QToolButton::clicked, this, &MainWindow::toolAddComponent);
connect(ui->btnGateNot, &QToolButton::clicked, this, &MainWindow::toolAddComponent);
connect(ui->btnGateAnd, &QToolButton::clicked, this, &MainWindow::toolAddComponent);
connect(ui->btnGateOr, &QToolButton::clicked, this, &MainWindow::toolAddComponent);
connect(ui->btnGateNand, &QToolButton::clicked, this, &MainWindow::toolAddComponent);
connect(ui->btnGateNor, &QToolButton::clicked, this, &MainWindow::toolAddComponent);
connect(ui->btnGateXor, &QToolButton::clicked, this, &MainWindow::toolAddComponent);
connect(ui->btnGateXnor, &QToolButton::clicked, this, &MainWindow::toolAddComponent);
connect(ui->btnIOHighConstant, &QToolButton::clicked, this, &MainWindow::toolAddComponent);
connect(ui->btnIOLowConstant, &QToolButton::clicked, this, &MainWindow::toolAddComponent);
connect(ui->btnIOLightBulb, &QToolButton::clicked, this, &MainWindow::toolAddComponent);
connect(ui->btnIOToggleButton, &QToolButton::clicked, this, &MainWindow::toolAddComponent);
//END CONNECT EVENTS
}
MainWindow::~MainWindow()
{
delete ui;
}
bool MainWindow::isFileLoaded()
{
return m_fileLoaded;
}
QString MainWindow::currFilename()
{
return m_currFilename;
}
//PUBLIC
void MainWindow::resetTools()
{
// Reset button checked states
ui->actionConnect->setChecked(false);
ui->actionRemove_Connections->setChecked(false);
ui->actionSelect->setChecked(false);
ui->actionPan->setChecked(false);
// Reset drag mode
ui->graphicsView->setDragMode(QGraphicsView::NoDrag);
// Unselect selected items
for(auto item : m_scene->selectedItems())
item->setSelected(false);
// Reset graphicsView interactivity
ui->graphicsView->setInteractive(true);
//Reset cursor shape
ui->graphicsView->setCursor(QCursor(Qt::CursorShape::ArrowCursor));
}
void MainWindow::changeMade()
{
unsavedChanges = true;
if(m_fileLoaded)
setWindowTitle(m_title + " - " + QFileInfo(m_currFilename).fileName() + "*");
else
setWindowTitle(m_title + " - Unsaved Circuit*");
}
void MainWindow::changesSaved()
{
unsavedChanges = false;
setWindowTitle(m_title + " - " + QFileInfo(m_currFilename).fileName());
}
void MainWindow::closeEvent(QCloseEvent *event)
{
if(!unsavedChanges)
{
event->accept();
return;
}
// I just blatantly ripped off the following code from this example https://doc.qt.io/qt-5/qmessagebox.html
QMessageBox msgBox;
msgBox.setText("The Circuit has unsaved changes.");
msgBox.setInformativeText("Do you really want to quit?");
msgBox.setStandardButtons(QMessageBox::Save | QMessageBox::Discard | QMessageBox::Cancel);
msgBox.setDefaultButton(QMessageBox::Save);
int ret = msgBox.exec();
switch (ret)
{
case QMessageBox::Save:
fileSave();
if(!unsavedChanges)
event->accept();
event->ignore();
break;
case QMessageBox::Discard:
event->accept();
break;
case QMessageBox::Cancel:
event->ignore();
break;
}
}
void MainWindow::reloadLocalICs()
{
// Return if no file is loaded
if(!m_fileLoaded)
return;
if(tabICInfoLabelExists)
{
delete ui->tabICInfoLabel;
tabICInfoLabelExists = false;
}
for(auto btn : m_icPushButtons)
{
delete btn;
}
m_icPushButtons.clear();
QDir fileDir(m_currFilename);
fileDir.cdUp();
QStringList nameFilter("*.csim");
QStringList icFiles = fileDir.entryList(nameFilter, QDir::Files);
for(const auto& icFile : icFiles)
{
// Skip step if file name is equal to currently loaded file
if(icFile == QFileInfo(m_currFilename).fileName())
continue;
QPushButton* btn = new QPushButton(ui->tabICScrollAreaWidgetContents);
ui->tabICScrollAreaWidgetContents->layout()->addWidget(btn);
btn->setText(icFile);
btn->setObjectName(icFile);
btn->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Maximum);
m_icPushButtons.append(btn);
connect(btn, &QPushButton::clicked, this, &MainWindow::toolAddIC);
}
}
//SIGNALS
void MainWindow::updateMatrix()
{
qreal scale = qPow(qreal(2), qreal(ui->zoomSlider->value() - 100) / qreal(20));
ui->labelZoomLevel->setText(QString::number(int(scale * 100.0)) + '%');
QMatrix matrix;
matrix.scale(scale, scale);
ui->graphicsView->setMatrix(matrix);
}
void MainWindow::zoomIn()
{
ui->zoomSlider->setValue(ui->zoomSlider->value() + ui->zoomSlider->pageStep());
}
void MainWindow::zoomOut()
{
ui->zoomSlider->setValue(ui->zoomSlider->value() - ui->zoomSlider->pageStep());
}
void MainWindow::resetZoom()
{
ui->zoomSlider->setValue(100);
}
void MainWindow::toggleUndoView()
{
if(undoView->isHidden())
undoView->show();
else
undoView->hide();
}
void MainWindow::editUndo()
{
m_scene->undo();
}
void MainWindow::editRedo()
{
m_scene->redo();
}
void MainWindow::editCut()
{
// Put parts into copy buffer
editCopy();
// Remove selected parts
QList<QGraphicsItem*> selItems = m_scene->selectedItems();
QList<Part*> selParts;
for(auto convItm : selItems)
selParts.append((Part*)convItm);
m_scene->removeParts(selParts);
}
void MainWindow::editCopy()
{
QList<QGraphicsItem*> selItems = m_scene->selectedItems();
QSet<Part*> selParts;
QList<Part*> selPartsList;
QList<Wire*> selPartsWires;
for(auto item : selItems)
selParts.insert((Part*)item);
selPartsList = selParts.values();
// Problem: find all wires of which both sides are being used by selected parts
// Go through all selected parts
for(auto part : selPartsList)
{
// Go through their outgoing connectors
for(auto outConnector : part->m_outputs)
{
// Go through their wires
for(auto outWire : outConnector->m_wires)
{
// See if the other end of the wire contains a part which is also selected
if(selParts.contains((Part*)outWire->m_connectorOutput->parentItem()))
selPartsWires.append(outWire);
}
}
}
m_scene->initCopy(selPartsList, selPartsWires);
}
void MainWindow::editPaste()
{
// Only do copy relative to mouse if graphicsView under mouse
// Else, copy into center of graphics view
m_scene->doCopy(ui->graphicsView->underMouse());
}
void MainWindow::editDelete()
{
QList<QGraphicsItem*> selItems = m_scene->selectedItems();
QList<Part*> selParts;
for(auto convItm : selItems)
selParts.append((Part*)convItm);
m_scene->removeParts(selParts);
}
void MainWindow::fileNew()
{
MainWindow* newWindow = new MainWindow();
newWindow->show();
}
void MainWindow::fileOpen()
{
QString filter = tr("CSIM Files(*.csim)");
auto filename = QFileDialog::getOpenFileName(this, tr("Open File"), "", filter, &filter);
if(!filename.isEmpty())
{
// If there is already a file loaded or there are unsaved changes, create a new window
if(m_fileLoaded || unsavedChanges)
{
MainWindow* newWindow = new MainWindow(nullptr, filename);
newWindow->show();
}
// Else, load file into current window
else
{
if(m_scene->m_logic->loadFromFile(filename))
{
m_currFilename = filename;
m_fileLoaded = true;
setWindowTitle(m_title + " - " + QFileInfo(m_currFilename).fileName());
}
}
}
reloadLocalICs();
}
void MainWindow::fileSave()
{
if(m_fileLoaded)
m_scene->m_logic->saveToFile(m_currFilename);
else
fileSaveAs();
reloadLocalICs();
}
void MainWindow::fileSaveAs()
{
QString filter = tr("CSIM Files(*.csim)");
auto filename = QFileDialog::getSaveFileName(this, tr("Save As"), "", filter, &filter);
if(!filename.isEmpty())
{
if(m_scene->m_logic->saveToFile(filename))
{
m_fileLoaded = true;
m_currFilename = filename;
setWindowTitle("Circuit Simulator - " + QFileInfo(m_currFilename).fileName());
}
}
reloadLocalICs();
}
void MainWindow::fileExit()
{
this->close();
}
void MainWindow::toolConnect()
{
resetTools();
toolMode = Connect;
ui->actionConnect->setChecked(true);
ui->graphicsView->setCursor(QCursor(Qt::CursorShape::PointingHandCursor));
}
void MainWindow::toolDisconnect()
{
resetTools();
toolMode = Disconnect;
ui->actionRemove_Connections->setChecked(true);
ui->graphicsView->setCursor(QCursor(Qt::CursorShape::PointingHandCursor));
}
void MainWindow::toolSelect()
{
resetTools();
toolMode = Select;
ui->graphicsView->setDragMode(QGraphicsView::RubberBandDrag);
ui->actionSelect->setChecked(true);
}
void MainWindow::toolPan()
{
resetTools();
toolMode = Pan;
ui->graphicsView->setDragMode(QGraphicsView::ScrollHandDrag);
ui->graphicsView->setInteractive(false);
ui->actionPan->setChecked(true);
}
void MainWindow::toolAddComponent()
{
QToolButton *button = (QToolButton*)sender();
if(button->objectName() == "btnGateBuffer")
m_scene->addPart(PartType::GateBuffer);
else if(button->objectName() == "btnGateNot")
m_scene->addPart(PartType::GateNot);
else if(button->objectName() == "btnGateAnd")
m_scene->addPart(PartType::GateAnd);
else if(button->objectName() == "btnGateOr")
m_scene->addPart(PartType::GateOr);
else if(button->objectName() == "btnGateNand")
m_scene->addPart(PartType::GateNand);
else if(button->objectName() == "btnGateNor")
m_scene->addPart(PartType::GateNor);
else if(button->objectName() == "btnGateXor")
m_scene->addPart(PartType::GateXor);
else if(button->objectName() == "btnGateXnor")
m_scene->addPart(PartType::GateXnor);
else if(button->objectName() == "btnIOHighConstant")
m_scene->addPart(PartType::IOHighConstant);
else if(button->objectName() == "btnIOLowConstant")
m_scene->addPart(PartType::IOLowConstant);
else if(button->objectName() == "btnIOLightBulb")
m_scene->addPart(PartType::IOLightBulb);
else if(button->objectName() == "btnIOToggleButton")
m_scene->addPart(PartType::IOToggleButton);
}
void MainWindow::simStep()
{
m_scene->m_logic->doLogicStep();
}
void MainWindow::simStart()
{
ui->actionStart->setEnabled(false);
ui->actionStep->setEnabled(false);
ui->actionStop->setEnabled(true);
simRunning = true;
}
void MainWindow::simStop()
{
ui->actionStart->setEnabled(true);
ui->actionStep->setEnabled(true);
ui->actionStop->setEnabled(false);
simRunning = false;
}
void MainWindow::onLogicTimerTimeout()
{
if(simRunning)
m_scene->m_logic->doLogicStep();
}
void MainWindow::onGfxTimerTimeout()
{
m_scene->updateGraphics();
}
void MainWindow::toolAddIC()
{
QDir fileDir(m_currFilename);
fileDir.cdUp();
QString path = fileDir.absoluteFilePath(sender()->objectName());
m_scene->addIC(path);
}
//END SIGNALS