/** * client/src/mainwindow.cpp * * This file is part of the traintastic source code. * * Copyright (C) 2019-2022 Reinder Feenstra * * 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. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "mainwindow.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "mdiarea.hpp" #include "dialog/connectdialog.hpp" #include "settings/settingsdialog.hpp" #include "dialog/worldlistdialog.hpp" #include "network/connection.hpp" #include "network/object.hpp" #include "network/property.hpp" #include "network/method.hpp" #include "subwindow/objectsubwindow.hpp" #include "subwindow/boardsubwindow.hpp" #include "subwindow/throttlesubwindow.hpp" #include "widget/serverlogwidget.hpp" #include "utils/menu.hpp" #include "theme/theme.hpp" #include #include #include #include #define SETTING_PREFIX "mainwindow/" #define SETTING_GEOMETRY SETTING_PREFIX "geometry" #define SETTING_WINDOWSTATE SETTING_PREFIX "windowstate" #define SETTING_VIEW_TOOLBAR SETTING_PREFIX "view_toolbar" static const QLatin1String dotCTW{".ctw"}; inline static void setWorldMute(const ObjectPtr& world, bool value) { if(Q_LIKELY(world)) world->setPropertyValue("mute", value); } inline static void setWorldNoSmoke(const ObjectPtr& world, bool value) { if(Q_LIKELY(world)) world->setPropertyValue("no_smoke", value); } template static SubWindow* createSubWindow(SubWindowType type, Args... args) { switch(type) { case SubWindowType::Object: return ObjectSubWindow::create(args...); case SubWindowType::Board: return BoardSubWindow::create(args...); case SubWindowType::Throttle: return ThrottleSubWindow::create(args...); } return nullptr; } MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent), m_splitter{new QSplitter(Qt::Vertical, this)}, m_mdiArea{new MdiArea(m_splitter)}, m_serverLog{nullptr}, m_toolbar{new QToolBar(this)} { instance = this; setWindowIcon(Theme::getIcon("appicon")); updateWindowTitle(); QMenu* menu; m_mdiArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded); m_mdiArea->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); // build the main menu { setMenuBar(new QMenuBar()); //QMenu* subMenu; //QAction* act; menu = menuBar()->addMenu(Locale::tr("qtapp.mainmenu:file")); m_actionConnectToServer = menu->addAction(Locale::tr("qtapp.mainmenu:connect_to_server") + "...", this, [this](){ MainWindow::connectToServer(); }); m_actionDisconnectFromServer = menu->addAction(Locale::tr("qtapp.mainmenu:disconnect_from_server"), this, &MainWindow::disconnectFromServer); menu->addSeparator(); m_actionNewWorld = menu->addAction(Theme::getIcon("world_new"), Locale::tr("qtapp.mainmenu:new_world"), [this]() { if(m_connection) if(const ObjectPtr& traintastic = m_connection->traintastic()) if(Method* method = traintastic->getMethod("new_world")) method->call(); }); m_actionNewWorld->setShortcut(QKeySequence::New); m_actionLoadWorld = menu->addAction(Theme::getIcon("world_load"), Locale::tr("qtapp.mainmenu:load_world") + "...", this, &MainWindow::loadWorld); m_actionLoadWorld->setShortcut(QKeySequence::Open); m_actionSaveWorld = menu->addAction(Theme::getIcon("world_save"), Locale::tr("qtapp.mainmenu:save_world"), [this]() { if(m_world) if(Method* method = m_world->getMethod("save")) method->call(); }); m_actionSaveWorld->setShortcut(QKeySequence::Save); m_actionCloseWorld = menu->addAction(Theme::getIcon("world_close"), Locale::tr("qtapp.mainmenu:close_world"), [this]() { if(m_connection) if(const ObjectPtr& traintastic = m_connection->traintastic()) traintastic->callMethod("close_world"); }); m_actionCloseWorld->setShortcut(QKeySequence::Close); menu->addSeparator(); m_actionImportWorld = menu->addAction(Theme::getIcon("world_import"), Locale::tr("qtapp.mainmenu:import_world") + "...", [this]() { QSettings settings; settings.beginGroup("import"); const QString pathKey{"path"}; const QString filename = QFileDialog::getOpenFileName( this, Locale::tr("qtapp.mainmenu:import_world"), settings.value(pathKey, QStandardPaths::standardLocations(QStandardPaths::HomeLocation).first()).toString(), Locale::tr("qtapp:traintastic_world").append(" (*.ctw)")); if(!filename.isEmpty()) { settings.setValue(pathKey, QFileInfo(filename).absolutePath()); QFile file(filename); if(file.open(QIODevice::ReadOnly)) { auto request = Message::newRequest(Message::Command::ImportWorld); request->write(file.readAll()); m_connection->send(request, [this](const std::shared_ptr& response) { if(response->isResponse() && response->isError()) { QMessageBox::critical( this, Locale::tr("qtapp:import_world_failed"), Locale::tr("qtapp.error:server_error_x").arg(static_cast>(response->errorCode()))); } }); } else { QMessageBox::critical( this, Locale::tr("qtapp:import_world_failed"), Locale::tr("qtapp.error:cant_read_from_file_x").arg(filename)); } } }); m_actionExportWorld = menu->addAction(Theme::getIcon("world_export"), Locale::tr("qtapp.mainmenu:export_world") + "...", [this]() { if(m_world) { assert(m_world->getProperty("name")); QSettings settings; settings.beginGroup("export"); const QString pathKey{"path"}; QString filename = settings.value(pathKey, QStandardPaths::writableLocation(QStandardPaths::HomeLocation)).toString() .append(QDir::separator()) .append(m_world->getProperty("name")->toString()) .append(QDateTime::currentDateTime().toString(" yyyy-MM-dd")) .append(dotCTW); filename = QFileDialog::getSaveFileName( this, Locale::tr("qtapp.mainmenu:export_world"), filename, Locale::tr("qtapp:traintastic_world").append(" (*.ctw)")); if(!filename.isEmpty()) { settings.setValue(pathKey, QFileInfo(filename).absolutePath()); if(!filename.endsWith(dotCTW)) filename.append(dotCTW); auto request = Message::newRequest(Message::Command::ExportWorld); m_connection->send(request, [this, filename](const std::shared_ptr& response) { if(response->isResponse() && !response->isError()) { QByteArray worldData; response->read(worldData); bool success = false; QSaveFile file(filename); if(file.open(QIODevice::WriteOnly | QIODevice::Truncate)) { const auto* p = worldData.constData(); const auto* end = p + worldData.size(); while(p < end) { qint64 r = file.write(p, end - p); if(r < 0) break; p += r; } success = file.commit(); } if(!success) { QMessageBox::critical( this, Locale::tr("qtapp:export_world_failed"), Locale::tr("qtapp.error:cant_write_to_file_x").arg(filename)); } } else { QMessageBox::critical( this, Locale::tr("qtapp:export_world_failed"), Locale::tr("qtapp.error:server_error_x").arg(static_cast>(response->errorCode()))); } }); } } }); menu->addSeparator(); menu->addAction(Locale::tr("qtapp.mainmenu:quit"), this, &MainWindow::close)->setShortcut(QKeySequence::Quit); menu = menuBar()->addMenu(Locale::tr("qtapp.mainmenu:view")); m_actionFullScreen = menu->addAction(Locale::tr("qtapp.mainmenu:fullscreen"), this, &MainWindow::toggleFullScreen); m_actionFullScreen->setCheckable(true); m_actionFullScreen->setShortcut(Qt::Key_F11); m_actionViewToolbar = menu->addAction(Locale::tr("qtapp.mainmenu:toolbar"), m_toolbar, &QToolBar::setVisible); m_actionViewToolbar->setCheckable(true); m_actionServerLog = menu->addAction(Locale::tr("qtapp.mainmenu:server_log") + "...", this, &MainWindow::toggleServerLog); m_actionServerLog->setCheckable(true); m_actionServerLog->setShortcut(Qt::Key_F12); m_menuWorld = menuBar()->addMenu(Locale::tr("qtapp.mainmenu:world")); m_menuConnection = m_menuWorld->addMenu(Locale::tr("qtapp.mainmenu:connection")); m_worldOnlineAction = m_menuConnection->addAction(Theme::getIcon("online"), Locale::tr("world:online"), [this]() { if(Q_LIKELY(m_world)) m_world->callMethod("online"); }); m_worldOfflineAction = m_menuConnection->addAction(Theme::getIcon("offline"), Locale::tr("world:offline"), [this]() { if(Q_LIKELY(m_world)) m_world->callMethod("offline"); }); m_menuPower = m_menuWorld->addMenu(Locale::tr("qtapp.mainmenu:power")); m_worldPowerOnAction = m_menuPower->addAction(Theme::getIcon("power_on"), Locale::tr("world:power_on"), [this]() { if(Q_LIKELY(m_world)) m_world->callMethod("power_on"); }); m_worldPowerOffAction = m_menuPower->addAction(Theme::getIcon("power_off"), Locale::tr("world:power_off"), [this]() { if(Q_LIKELY(m_world)) m_world->callMethod("power_off"); }); m_menuWorld->addSeparator(); m_worldStopAction = m_menuWorld->addAction(Theme::getIcon("stop"), Locale::tr("world:stop"), [this]() { if(Q_LIKELY(m_world)) { m_world->callMethod("stop"); if(AbstractProperty* p = m_world->getProperty("state")) m_worldStopAction->setChecked(!contains(p->toSet(), WorldState::Run)); // make sure checked matches current world state } }); m_worldStopAction->setCheckable(true); m_worldRunAction = m_menuWorld->addAction(Theme::getIcon("run"), Locale::tr("world:run"), [this]() { if(Q_LIKELY(m_world)) { m_world->callMethod("run"); if(AbstractProperty* p = m_world->getProperty("state")) m_worldRunAction->setChecked(contains(p->toSet(), WorldState::Run)); // make sure checked matches current world state } }); m_worldRunAction->setCheckable(true); m_menuWorld->addSeparator(); m_worldMuteMenuAction = m_menuWorld->addAction(Theme::getIcon("mute"), Locale::tr("world:mute"), [this](bool checked) { setWorldMute(m_world, checked); }); m_worldMuteMenuAction->setCheckable(true); m_worldNoSmokeMenuAction = m_menuWorld->addAction(Theme::getIcon("no_smoke"), Locale::tr("world:no_smoke"), [this](bool checked) { setWorldNoSmoke(m_world, checked); }); m_worldNoSmokeMenuAction->setCheckable(true); m_worldEditAction = m_menuWorld->addAction(Theme::getIcon("edit"), Locale::tr("world:edit"), [this](bool checked) { if(m_world) if(AbstractProperty* property = m_world->getProperty("edit")) property->setValueBool(checked); }); m_worldEditAction->setCheckable(true); m_menuWorld->addSeparator(); m_worldSimulationAction = m_menuWorld->addAction(Theme::getIcon("simulation"), Locale::tr("world:simulation"), [this](bool checked) { if(m_world) if(AbstractProperty* property = m_world->getProperty("simulation")) property->setValueBool(checked); }); m_worldSimulationAction->setCheckable(true); m_menuWorld->addSeparator(); m_menuWorld->addAction(Theme::getIcon("world"), Locale::tr("qtapp.mainmenu:world_properties"), [this](){ showObject("world", Locale::tr("qtapp.mainmenu:world_properties")); }); m_menuObjects = menuBar()->addMenu(Locale::tr("qtapp.mainmenu:objects")); menu = m_menuObjects->addMenu(Theme::getIcon("hardware"), Locale::tr("qtapp.mainmenu:hardware")); menu->addAction(Locale::tr("world:interfaces") + "...", [this](){ showObject("world.interfaces", Locale::tr("world:interfaces")); }); menu->addAction(Locale::tr("world:decoders") + "...", [this](){ showObject("world.decoders", Locale::tr("world:decoders")); }); menu->addAction(Locale::tr("world:inputs") + "...", [this](){ showObject("world.inputs", Locale::tr("world:inputs")); }); menu->addAction(Locale::tr("world:outputs") + "...", [this](){ showObject("world.outputs", Locale::tr("world:outputs")); }); m_menuObjects->addAction(Locale::tr("world:boards") + "...", [this](){ showObject("world.boards", Locale::tr("world:boards")); }); m_menuObjects->addAction(Locale::tr("world:clock") + "...", [this](){ showObject("world.clock", Locale::tr("world:clock")); }); m_menuObjects->addAction(Locale::tr("world:trains") + "...", [this](){ showObject("world.trains", Locale::tr("world:trains")); }); m_menuObjects->addAction(Locale::tr("world:rail_vehicles") + "...", [this](){ showObject("world.rail_vehicles", Locale::tr("world:rail_vehicles")); }); m_actionLuaScript = m_menuObjects->addAction(Theme::getIcon("lua"), Locale::tr("world:lua_scripts") + "...", [this](){ showObject("world.lua_scripts", Locale::tr("world:lua_scripts")); }); menu = menuBar()->addMenu(Locale::tr("qtapp.mainmenu:tools")); menu->addAction(Locale::tr("qtapp.mainmenu:settings") + "...", [this]() { std::unique_ptr d = std::make_unique(this); d->exec(); }); menu->addSeparator(); m_menuServer = menu->addMenu(Locale::tr("qtapp.mainmenu:server")); m_actionServerSettings = m_menuServer->addAction(Locale::tr("qtapp.mainmenu:server_settings") + "...", this, [this]() { showObject("traintastic.settings", Locale::tr("qtapp.mainmenu:server_settings")); }); m_menuServer->addSeparator(); m_actionServerRestart = m_menuServer->addAction(Locale::tr("qtapp.mainmenu:restart_server"), this, [this]() { if(QMessageBox::question(this, Locale::tr("qtapp.mainmenu:restart_server"), Locale::tr("qtapp.mainmenu:restart_server_question"), Locale::tr("qtapp.message_box:yes"), Locale::tr("qtapp.message_box:no"), "", 0, 1) != 0) return; if(m_connection) if(const ObjectPtr& traintastic = m_connection->traintastic()) if(Method* method = traintastic->getMethod("restart")) method->call(); }); m_actionServerShutdown = m_menuServer->addAction(Locale::tr("qtapp.mainmenu:shutdown_server"), this, [this]() { if(QMessageBox::question(this, Locale::tr("qtapp.mainmenu:shutdown_server"), Locale::tr("qtapp.mainmenu:shutdown_server_question"), Locale::tr("qtapp.message_box:yes"), Locale::tr("qtapp.message_box:no"), "", 0, 1) != 0) return; if(m_connection) if(const ObjectPtr& traintastic = m_connection->traintastic()) if(Method* method = traintastic->getMethod("shutdown")) method->call(); }); menu = menuBar()->addMenu(Locale::tr("qtapp.mainmenu:help")); menu->addAction(Locale::tr("qtapp.mainmenu:help"), [](){ QDesktopServices::openUrl("https://traintastic.org/manual?version=" + QApplication::applicationVersion() + "&codename=" TRAINTASTIC_CODENAME); })->setShortcut(QKeySequence::HelpContents); //menu->addSeparator(); //menu->addAction(Locale::tr("qtapp.mainmenu:about_qt") + "...", qApp, &QApplication::aboutQt); menu->addAction(Locale::tr("qtapp.mainmenu:about") + "...", this, &MainWindow::showAbout); } // Online/offline: m_worldOnlineOfflineToolButton = new QToolButton(this); m_worldOnlineOfflineToolButton->setIcon(Theme::getIcon("offline")); m_worldOnlineOfflineToolButton->setToolTip(Locale::tr("qtapp:toggle_offline_online")); m_worldOnlineOfflineToolButton->setPopupMode(QToolButton::MenuButtonPopup); connect(m_worldOnlineOfflineToolButton, &QToolButton::clicked, this, [this]() { if(Q_LIKELY(m_world)) if(auto* state = m_world->getProperty("state")) m_world->callMethod(contains(state->toSet(), WorldState::Online) ? "offline" : "online"); }); m_worldOnlineOfflineToolButton->setMenu(createMenu(this, {m_worldOnlineAction, m_worldOfflineAction})); m_toolbar->addWidget(m_worldOnlineOfflineToolButton); // Power on/off: m_worldPowerOnOffToolButton = new QToolButton(this); m_worldPowerOnOffToolButton->setIcon(Theme::getIcon("power_off")); m_worldPowerOnOffToolButton->setToolTip(Locale::tr("qtapp:toggle_power")); m_worldPowerOnOffToolButton->setPopupMode(QToolButton::MenuButtonPopup); connect(m_worldPowerOnOffToolButton, &QToolButton::clicked, this, [this]() { if(Q_LIKELY(m_world)) if(auto* state = m_world->getProperty("state")) m_world->callMethod(contains(state->toSet(), WorldState::PowerOn) ? "power_off" : "power_on"); }); m_worldPowerOnOffToolButton->setMenu(createMenu(this, {m_worldPowerOnAction, m_worldPowerOffAction})); m_toolbar->addWidget(m_worldPowerOnOffToolButton); m_toolbar->addSeparator(); m_toolbar->addAction(m_worldStopAction); m_toolbar->addAction(m_worldRunAction); m_toolbar->addSeparator(); m_worldMuteToolbarAction = m_toolbar->addAction(Theme::getIcon("unmute", "mute"), Locale::tr("qtapp:toggle_mute"), [this](bool checked) { setWorldMute(m_world, checked); }); m_worldMuteToolbarAction->setCheckable(true); m_worldNoSmokeToolbarAction = m_toolbar->addAction(Theme::getIcon("smoke", "no_smoke"), Locale::tr("qtapp:toggle_smoke"), [this](bool checked) { setWorldNoSmoke(m_world, checked); }); m_worldNoSmokeToolbarAction->setCheckable(true); QWidget* spacer = new QWidget(this); spacer->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); spacer->show(); m_toolbar->addWidget(spacer); m_toolbar->addAction(m_worldEditAction); QVBoxLayout* l = new QVBoxLayout(); l->setMargin(0); l->addWidget(m_toolbar); l->addWidget(m_mdiArea); QWidget* w = new QWidget(); w->setLayout(l); m_splitter->addWidget(w); m_splitter->setCollapsible(0, false); setCentralWidget(m_splitter); setStatusBar(new QStatusBar()); QSettings settings; if(settings.contains(SETTING_GEOMETRY)) restoreGeometry(settings.value(SETTING_GEOMETRY).toByteArray()); if(settings.contains(SETTING_WINDOWSTATE)) setWindowState(static_cast(settings.value(SETTING_WINDOWSTATE).toInt())); m_actionFullScreen->setChecked(isFullScreen()); m_actionViewToolbar->setChecked(settings.value(SETTING_VIEW_TOOLBAR, true).toBool()); m_toolbar->setVisible(m_actionViewToolbar->isChecked()); connectionStateChanged(); } MainWindow::~MainWindow() { for(SubWindow* window : m_subWindows) disconnect(window, &QMdiSubWindow::destroyed, nullptr, nullptr); } void MainWindow::connectToServer(const QString& url) { if(m_connection) return; std::unique_ptr d = std::make_unique(this, url); if(d->exec() == QDialog::Accepted) { m_connection = d->connection(); connect(m_connection.get(), &Connection::worldChanged, this, [this]() { worldChanged(); updateActions(); }); connect(m_connection.get(), &Connection::stateChanged, this, &MainWindow::connectionStateChanged); worldChanged(); connectionStateChanged(); } } void MainWindow::disconnectFromServer() { if(m_connection) m_connection->disconnectFromHost(); } void MainWindow::closeEvent(QCloseEvent* event) { QSettings settings; settings.setValue(SETTING_GEOMETRY, saveGeometry()); settings.setValue(SETTING_WINDOWSTATE, static_cast(windowState())); settings.setValue(SETTING_VIEW_TOOLBAR, m_toolbar->isVisible()); QMainWindow::closeEvent(event); } void MainWindow::changeEvent(QEvent* event) { if(event->type() == QEvent::WindowStateChange) m_actionFullScreen->setChecked(isFullScreen()); } void MainWindow::worldChanged() { if(m_world) m_mdiArea->closeAllSubWindows(); if(m_connection) m_world = m_connection->world(); else m_world.reset(); if(m_world) { if(auto* state = m_world->getProperty("state")) connect(state, &AbstractProperty::valueChangedInt64, this, &MainWindow::worldStateChanged); if(AbstractProperty* simulation = m_world->getProperty("simulation")) { connect(simulation, &AbstractProperty::attributeChanged, [this](AttributeName attribute, const QVariant& value) { if(attribute == AttributeName::Enabled) m_worldSimulationAction->setEnabled(value.toBool()); }); m_worldSimulationAction->setEnabled(simulation->getAttributeBool(AttributeName::Enabled, true)); } } updateWindowTitle(); } void MainWindow::updateWindowTitle() { QString title = "Traintastic v" + QApplication::applicationVersion(); if(m_world) title = m_world->getProperty("name")->toString() + " - " + title; setWindowTitle(title); } void MainWindow::loadWorld() { if(!m_connection) return; std::unique_ptr d = std::make_unique(m_connection, this); if(d->exec() == QDialog::Accepted) { Method* method = m_connection->traintastic()->getMethod("load_world"); if(Q_LIKELY(method)) method->call(d->uuid()); } } void MainWindow::toggleFullScreen() { const bool fullScreen = qobject_cast(sender())->isChecked(); if(fullScreen == isFullScreen()) return; if(fullScreen) { m_beforeFullScreenGeometry = saveGeometry(); showFullScreen(); } else { showNormal(); // required to exit fullscreen mode if(!m_beforeFullScreenGeometry.isEmpty()) restoreGeometry(m_beforeFullScreenGeometry); else showMaximized(); } } void MainWindow::toggleServerLog() { if(m_serverLog) { delete m_serverLog; m_serverLog = nullptr; m_actionServerLog->setChecked(false); } else { m_serverLog = new ServerLogWidget(connection(), m_splitter); m_splitter->addWidget(m_serverLog); m_actionServerLog->setChecked(true); } } /* void MainWindow::showObjectEdit(const QString& id) { if(!m_mdiSubWindow.objectEdit.contains(id)) { ObjectEditSubWindow* window = new ObjectEditSubWindow(id); m_mdiArea->addSubWindow(window); window->setAttribute(Qt::WA_DeleteOnClose); connect(window, &QMdiSubWindow::destroyed, [this, id](QObject*){ m_mdiSubWindow.objectEdit.remove(id); }); window->show(); m_mdiSubWindow.objectEdit[id] = window; } else m_mdiArea->setActiveSubWindow(m_mdiSubWindow.objectEdit[id]); } void MainWindow::showObjectEdit(const ObjectPtr& object) { const QString& id = object->getProperty("id")->toString(); if(!m_mdiSubWindow.objectEdit.contains(id)) { ObjectEditSubWindow* window = new ObjectEditSubWindow(object); m_mdiArea->addSubWindow(window); window->setAttribute(Qt::WA_DeleteOnClose); connect(window, &QMdiSubWindow::destroyed, [this, id](QObject*){ m_mdiSubWindow.objectEdit.remove(id); }); window->show(); m_mdiSubWindow.objectEdit[id] = window; } else m_mdiArea->setActiveSubWindow(m_mdiSubWindow.objectEdit[id]); } */ void MainWindow::showObject(const ObjectPtr& object, SubWindowType type) { QString windowId; if(auto* property = object->getProperty("id")) windowId = toString(type).append("/").append(property->toString()); if(windowId.isEmpty() || !m_subWindows.contains(windowId)) { SubWindow* window = createSubWindow(type, object); if(!window) return; if(!windowId.isEmpty()) m_subWindows[windowId] = window; m_mdiArea->addSubWindow(window); window->setAttribute(Qt::WA_DeleteOnClose); connect(window, &QMdiSubWindow::destroyed, this, [this, windowId](QObject*) { m_subWindows.remove(windowId); }); window->show(); } else m_mdiArea->setActiveSubWindow(m_subWindows[windowId]); } void MainWindow::showObject(const QString& id, const QString& title, SubWindowType type) { const QString windowId{toString(type).append("/").append(id)}; if(!m_subWindows.contains(windowId)) { SubWindow* window = createSubWindow(type, m_connection, id); if(!window) return; if(!title.isEmpty()) window->setWindowTitle(title); m_subWindows[windowId] = window; m_mdiArea->addSubWindow(window); window->setAttribute(Qt::WA_DeleteOnClose); connect(window, &QMdiSubWindow::destroyed, this, [this, windowId](QObject*) { m_subWindows.remove(windowId); }); window->show(); } else m_mdiArea->setActiveSubWindow(m_subWindows[windowId]); } void MainWindow::showAbout() { QMessageBox::about(this, tr("About Traintastic"), "

Traintastic v" + QApplication::applicationVersion() + " " TRAINTASTIC_CODENAME "

" "

" TRAINTASTIC_COPYRIGHT "

" "

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.

" "

This program is distributed in the hope that it will be useful," " but WITHOUT ANY WARRANTY; without even the implied warranty of" " MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the" " GNU General Public License for more details.

"); } void MainWindow::connectionStateChanged() { const bool connected = m_connection && m_connection->state() == Connection::State::Connected; m_mdiArea->setEnabled(connected); if(connected && m_actionServerLog->isChecked() && !m_serverLog) { m_serverLog = new ServerLogWidget(m_connection, m_splitter); m_splitter->addWidget(m_serverLog); } if(m_connection && m_connection->state() == Connection::State::Disconnected) { m_connection.reset(); if(m_serverLog) { delete m_serverLog; m_serverLog = nullptr; } worldChanged(); } updateActions(); //if(client.state() == Client::State::SocketError) // statusBar()->showMessage(client.errorString()); } void MainWindow::updateActions() { const bool connected = m_connection && m_connection->state() == Connection::State::Connected; const bool haveWorld = connected && m_connection->world(); m_actionConnectToServer->setEnabled(!m_connection); m_actionConnectToServer->setVisible(!connected); m_actionDisconnectFromServer->setVisible(connected); m_actionNewWorld->setEnabled(connected); m_actionLoadWorld->setEnabled(connected); m_actionSaveWorld->setEnabled(haveWorld); m_actionCloseWorld->setEnabled(haveWorld); m_actionImportWorld->setEnabled(connected); m_actionExportWorld->setEnabled(haveWorld); m_actionServerLog->setEnabled(connected); m_menuServer->setEnabled(connected); m_actionServerSettings->setEnabled(connected); if(connected) { Method* m; m = m_connection->traintastic()->getMethod("restart"); m_actionServerRestart->setEnabled(m && m->getAttributeBool(AttributeName::Enabled, false)); m = m_connection->traintastic()->getMethod("shutdown"); m_actionServerShutdown->setEnabled(m && m->getAttributeBool(AttributeName::Enabled, false)); } setMenuEnabled(m_menuWorld, haveWorld); m_worldOnlineOfflineToolButton->setEnabled(haveWorld); m_worldPowerOnOffToolButton->setEnabled(haveWorld); m_worldMuteToolbarAction->setEnabled(haveWorld); m_worldNoSmokeToolbarAction->setEnabled(haveWorld); worldStateChanged(haveWorld ? m_connection->world()->getProperty("state")->toInt64() : 0); setMenuEnabled(m_menuObjects, haveWorld); if(haveWorld && !m_connection->world()->hasProperty("lua_scripts")) m_actionLuaScript->setEnabled(false); if(connected && !haveWorld) { m_mdiArea->addBackgroundAction(m_actionNewWorld); m_mdiArea->addBackgroundAction(m_actionLoadWorld); } else { m_mdiArea->removeBackgroundAction(m_actionNewWorld); m_mdiArea->removeBackgroundAction(m_actionLoadWorld); } } void MainWindow::worldStateChanged(int64_t value) { const WorldState state = static_cast(value); QIcon connectionIcon = Theme::getIcon(contains(state, WorldState::Online) ? "online" : "offline"); QIcon powerIcon = Theme::getIcon(contains(state, WorldState::PowerOn) ? "power_on" : "power_off"); m_menuConnection->setIcon(connectionIcon); m_worldOnlineOfflineToolButton->setIcon(connectionIcon); m_menuPower->setIcon(powerIcon); m_worldPowerOnOffToolButton->setIcon(powerIcon); m_worldStopAction->setChecked(!contains(state, WorldState::Run)); m_worldRunAction->setChecked(contains(state, WorldState::Run)); m_worldMuteMenuAction->setChecked(contains(state, WorldState::Mute)); m_worldMuteToolbarAction->setChecked(contains(state, WorldState::Mute)); m_worldNoSmokeMenuAction->setChecked(contains(state, WorldState::NoSmoke)); m_worldNoSmokeToolbarAction->setChecked(contains(state, WorldState::NoSmoke)); m_worldEditAction->setChecked(contains(state, WorldState::Edit)); m_worldSimulationAction->setChecked(contains(state, WorldState::Simulation)); }