264 lines
9.0 KiB
C++
264 lines
9.0 KiB
C++
#include "MainWindow.h"
|
|
|
|
#include "NodePalette.h"
|
|
#include "PropertyPanel.h"
|
|
#include "core/FlowSolver.h"
|
|
#include "core/JsonIo.h"
|
|
#include "scene/DiagramScene.h"
|
|
#include "scene/GridView.h"
|
|
|
|
#include <QApplication>
|
|
#include <QCloseEvent>
|
|
#include <QDockWidget>
|
|
#include <QFileDialog>
|
|
#include <QLabel>
|
|
#include <QMenuBar>
|
|
#include <QMessageBox>
|
|
#include <QStatusBar>
|
|
#include <QToolBar>
|
|
|
|
namespace diag {
|
|
|
|
MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent)
|
|
{
|
|
setWindowTitle(tr("PipeDiagram"));
|
|
resize(1280, 800);
|
|
|
|
TypeRegistry& registry = TypeRegistry::instance();
|
|
if (registry.nodeTypes().isEmpty()) {
|
|
QString err;
|
|
if (!registry.loadFromFile(QStringLiteral(":/config/nodetypes.json"), &err))
|
|
QMessageBox::warning(this, tr("Configuration"),
|
|
tr("Failed to load node types: %1").arg(err));
|
|
}
|
|
|
|
m_model = new NetworkModel(®istry, this);
|
|
m_undoStack = new QUndoStack(this);
|
|
m_scene = new DiagramScene(m_model, m_undoStack, this);
|
|
m_view = new GridView(m_scene, this);
|
|
setCentralWidget(m_view);
|
|
|
|
m_palette = new NodePalette(®istry, this);
|
|
auto* paletteDock = new QDockWidget(tr("Node palette"), this);
|
|
paletteDock->setObjectName(QStringLiteral("paletteDock"));
|
|
paletteDock->setWidget(m_palette);
|
|
addDockWidget(Qt::LeftDockWidgetArea, paletteDock);
|
|
|
|
m_properties = new PropertyPanel(this);
|
|
m_properties->setScene(m_scene);
|
|
auto* propsDock = new QDockWidget(tr("Properties"), this);
|
|
propsDock->setObjectName(QStringLiteral("propsDock"));
|
|
propsDock->setWidget(m_properties);
|
|
addDockWidget(Qt::RightDockWidgetArea, propsDock);
|
|
|
|
buildActions();
|
|
|
|
auto* cursorLabel = new QLabel;
|
|
auto* zoomLabel = new QLabel(QStringLiteral("100%"));
|
|
statusBar()->addPermanentWidget(cursorLabel);
|
|
statusBar()->addPermanentWidget(zoomLabel);
|
|
connect(m_view, &GridView::cursorMoved, this, [cursorLabel](QPointF p) {
|
|
cursorLabel->setText(QStringLiteral("x: %1 y: %2").arg(int(p.x())).arg(int(p.y())));
|
|
});
|
|
connect(m_view, &GridView::zoomChanged, this, [zoomLabel](double z) {
|
|
zoomLabel->setText(QStringLiteral("%1%").arg(int(z * 100)));
|
|
});
|
|
connect(m_scene, &DiagramScene::connectionMessage, this,
|
|
[this](const QString& text) { statusBar()->showMessage(text, 4000); });
|
|
|
|
m_settings.load();
|
|
applySettings(m_settings);
|
|
}
|
|
|
|
void MainWindow::buildActions()
|
|
{
|
|
auto* fileMenu = menuBar()->addMenu(tr("&File"));
|
|
auto* toolbar = addToolBar(tr("Main"));
|
|
toolbar->setObjectName(QStringLiteral("mainToolbar"));
|
|
|
|
QAction* newAct = fileMenu->addAction(tr("&New"), QKeySequence::New, this,
|
|
&MainWindow::newDocument);
|
|
QAction* openAct = fileMenu->addAction(tr("&Open…"), QKeySequence::Open, this,
|
|
&MainWindow::openDocument);
|
|
QAction* saveAct = fileMenu->addAction(tr("&Save"), QKeySequence::Save, this,
|
|
&MainWindow::saveDocument);
|
|
fileMenu->addAction(tr("Save &As…"), QKeySequence::SaveAs, this,
|
|
&MainWindow::saveDocumentAs);
|
|
fileMenu->addAction(tr("Load Sa&mple"), this, &MainWindow::loadSample);
|
|
fileMenu->addSeparator();
|
|
fileMenu->addAction(tr("&Quit"), QKeySequence::Quit, this, &QWidget::close);
|
|
|
|
auto* editMenu = menuBar()->addMenu(tr("&Edit"));
|
|
QAction* undoAct = m_undoStack->createUndoAction(this, tr("&Undo"));
|
|
undoAct->setShortcut(QKeySequence::Undo);
|
|
QAction* redoAct = m_undoStack->createRedoAction(this, tr("&Redo"));
|
|
redoAct->setShortcut(QKeySequence::Redo);
|
|
editMenu->addAction(undoAct);
|
|
editMenu->addAction(redoAct);
|
|
editMenu->addSeparator();
|
|
editMenu->addAction(tr("Cu&t"), QKeySequence::Cut, m_scene, &DiagramScene::cutSelection);
|
|
editMenu->addAction(tr("&Copy"), QKeySequence::Copy, m_scene,
|
|
&DiagramScene::copySelection);
|
|
editMenu->addAction(tr("&Paste"), QKeySequence::Paste, m_scene, &DiagramScene::paste);
|
|
QAction* deleteAct = editMenu->addAction(tr("&Delete"), QKeySequence::Delete, m_scene,
|
|
&DiagramScene::deleteSelection);
|
|
editMenu->addAction(tr("Select &All"), QKeySequence::SelectAll, m_scene,
|
|
&DiagramScene::selectAll);
|
|
editMenu->addSeparator();
|
|
editMenu->addAction(tr("Se&ttings…"), QKeySequence::Preferences, this,
|
|
&MainWindow::showSettings);
|
|
|
|
auto* viewMenu = menuBar()->addMenu(tr("&View"));
|
|
viewMenu->addAction(tr("Zoom &In"), QKeySequence::ZoomIn, m_view, &GridView::zoomIn);
|
|
viewMenu->addAction(tr("Zoom &Out"), QKeySequence::ZoomOut, m_view, &GridView::zoomOut);
|
|
QAction* fitAct = viewMenu->addAction(tr("Zoom to &Fit"), QKeySequence(Qt::Key_F), m_view,
|
|
&GridView::zoomToFit);
|
|
viewMenu->addAction(tr("&Reset Zoom"), QKeySequence(Qt::CTRL | Qt::Key_0), m_view,
|
|
&GridView::zoomReset);
|
|
|
|
auto* simMenu = menuBar()->addMenu(tr("&Simulation"));
|
|
m_simulateAction = simMenu->addAction(tr("&Run Flow Simulation"));
|
|
m_simulateAction->setCheckable(true);
|
|
m_simulateAction->setShortcut(QKeySequence(Qt::Key_F5));
|
|
connect(m_simulateAction, &QAction::toggled, this, &MainWindow::runSimulation);
|
|
|
|
toolbar->addAction(newAct);
|
|
toolbar->addAction(openAct);
|
|
toolbar->addAction(saveAct);
|
|
toolbar->addSeparator();
|
|
toolbar->addAction(undoAct);
|
|
toolbar->addAction(redoAct);
|
|
toolbar->addAction(deleteAct);
|
|
toolbar->addSeparator();
|
|
toolbar->addAction(fitAct);
|
|
toolbar->addAction(m_simulateAction);
|
|
}
|
|
|
|
void MainWindow::applySettings(const EditorSettings& settings)
|
|
{
|
|
m_settings = settings;
|
|
m_scene->setRouteConfig(settings.route);
|
|
m_scene->setTheme(Theme::make(settings.scheme));
|
|
settings.save();
|
|
}
|
|
|
|
void MainWindow::showSettings()
|
|
{
|
|
SettingsDialog dialog(m_settings, this);
|
|
if (dialog.exec() == QDialog::Accepted)
|
|
applySettings(dialog.settings());
|
|
}
|
|
|
|
bool MainWindow::maybeSave()
|
|
{
|
|
if (m_undoStack->isClean())
|
|
return true;
|
|
const auto answer = QMessageBox::question(
|
|
this, tr("Unsaved changes"), tr("The diagram has unsaved changes. Save them?"),
|
|
QMessageBox::Save | QMessageBox::Discard | QMessageBox::Cancel);
|
|
if (answer == QMessageBox::Save) {
|
|
saveDocument();
|
|
return m_undoStack->isClean();
|
|
}
|
|
return answer == QMessageBox::Discard;
|
|
}
|
|
|
|
void MainWindow::newDocument()
|
|
{
|
|
if (!maybeSave())
|
|
return;
|
|
m_model->clear();
|
|
m_undoStack->clear();
|
|
m_currentFile.clear();
|
|
m_simulateAction->setChecked(false);
|
|
}
|
|
|
|
void MainWindow::openDocument()
|
|
{
|
|
if (!maybeSave())
|
|
return;
|
|
const QString path = QFileDialog::getOpenFileName(this, tr("Open diagram"), {},
|
|
tr("Diagrams (*.json)"));
|
|
if (path.isEmpty())
|
|
return;
|
|
QString err;
|
|
if (!JsonIo::load(m_model, path, &err)) {
|
|
QMessageBox::warning(this, tr("Open failed"), err);
|
|
return;
|
|
}
|
|
m_undoStack->clear();
|
|
m_currentFile = path;
|
|
m_simulateAction->setChecked(false);
|
|
m_view->zoomToFit();
|
|
}
|
|
|
|
void MainWindow::saveDocument()
|
|
{
|
|
if (m_currentFile.isEmpty()) {
|
|
saveDocumentAs();
|
|
return;
|
|
}
|
|
QString err;
|
|
if (!JsonIo::save(*m_model, m_currentFile, &err))
|
|
QMessageBox::warning(this, tr("Save failed"), err);
|
|
else
|
|
m_undoStack->setClean();
|
|
}
|
|
|
|
void MainWindow::saveDocumentAs()
|
|
{
|
|
const QString path = QFileDialog::getSaveFileName(this, tr("Save diagram"), {},
|
|
tr("Diagrams (*.json)"));
|
|
if (path.isEmpty())
|
|
return;
|
|
m_currentFile = path;
|
|
saveDocument();
|
|
}
|
|
|
|
void MainWindow::loadSample()
|
|
{
|
|
if (!maybeSave())
|
|
return;
|
|
QString err;
|
|
if (!JsonIo::load(m_model, QStringLiteral(":/samples/waterworks.json"), &err)) {
|
|
QMessageBox::warning(this, tr("Sample"), err);
|
|
return;
|
|
}
|
|
m_undoStack->clear();
|
|
m_currentFile.clear();
|
|
m_view->zoomToFit();
|
|
}
|
|
|
|
void MainWindow::setSimulationRunning(bool running)
|
|
{
|
|
m_simulateAction->setChecked(running);
|
|
}
|
|
|
|
void MainWindow::runSimulation(bool enabled)
|
|
{
|
|
if (enabled) {
|
|
// Mock calculation: distribute flow in every relation network, then
|
|
// publish rates to the read-only flowRate property and animate.
|
|
for (const Relation& rel : m_model->registry()->relations()) {
|
|
const FlowResult result = FlowSolver::solve(*m_model, rel.id);
|
|
if (!result.ok && !result.message.isEmpty())
|
|
statusBar()->showMessage(tr("%1: %2").arg(rel.name, result.message), 5000);
|
|
FlowSolver::apply(*m_model, result);
|
|
}
|
|
statusBar()->showMessage(tr("Flow simulation running"), 3000);
|
|
} else {
|
|
statusBar()->showMessage(tr("Flow simulation stopped"), 3000);
|
|
}
|
|
m_scene->setFlowAnimationEnabled(enabled);
|
|
}
|
|
|
|
void MainWindow::closeEvent(QCloseEvent* event)
|
|
{
|
|
if (maybeSave())
|
|
event->accept();
|
|
else
|
|
event->ignore();
|
|
}
|
|
|
|
} // namespace diag
|