ModelRailroadTimetablePlanner/src/app/mainwindow.cpp

1032 lines
32 KiB
C++

/*
* ModelRailroadTimetablePlanner
* Copyright 2016-2023, Filippo Gentile
*
* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include "app/session.h"
#include "viewmanager/viewmanager.h"
#include "jobs/jobeditor/jobpatheditor.h"
#include <QDockWidget>
#include "utils/owningqpointer.h"
#include <QMessageBox>
#include <QFileDialog>
#include "utils/files/recentdirstore.h"
#include "utils/files/file_format_names.h"
#include <QPushButton>
#include <QLabel>
#include <QCloseEvent>
#include "settings/settingsdialog.h"
#include "graph/model/linegraphmanager.h"
#include "graph/view/linegraphwidget.h"
#include "stations/manager/segments/model/railwaysegmenthelper.h"
#include "db_metadata/meetinginformationdialog.h"
#include "printing/wizard/printwizard.h"
#ifdef ENABLE_USER_QUERY
# include "sqlconsole/sqlconsole.h"
#endif
#include <QActionGroup>
#include "utils/delegates/sql/customcompletionlineedit.h"
#include "searchbox/searchresultmodel.h"
#ifdef ENABLE_BACKGROUND_MANAGER
# include "backgroundmanager/backgroundmanager.h"
# include "backgroundmanager/backgroundresultpanel.h"
# include "jobs/jobs_checker/crossing/jobcrossingchecker.h"
# include "rollingstock/rs_checker/rscheckermanager.h"
#endif // ENABLE_BACKGROUND_MANAGER
#include "propertiesdialog.h"
#include "info.h"
#include <QThreadPool>
#include <QTimer> //HACK: TODO remove
#include "app/scopedebug.h"
namespace directory_key {
const QLatin1String session = QLatin1String("session_dir_key");
} // namespace directory_key
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow),
jobEditor(nullptr),
#ifdef ENABLE_BACKGROUND_MANAGER
resPanelDock(nullptr),
#endif // ENABLE_BACKGROUND_MANAGER
view(nullptr),
jobDock(nullptr),
searchEdit(nullptr),
welcomeLabel(nullptr),
recentFileActs{nullptr},
m_mode(CentralWidgetMode::StartPageMode),
closeTimerId(0)
{
ui->setupUi(this);
ui->actionAbout->setText(tr("About %1").arg(qApp->applicationDisplayName()));
auto viewMgr = Session->getViewManager();
viewMgr->m_mainWidget = this;
auto graphMgr = viewMgr->getLineGraphMgr();
connect(graphMgr, &LineGraphManager::jobSelected, this, &MainWindow::onJobSelected);
// view = graphMgr->getView();
view = new LineGraphWidget(this);
// Welcome label
welcomeLabel = new QLabel(this);
welcomeLabel->setTextFormat(Qt::RichText);
welcomeLabel->setAlignment(Qt::AlignCenter);
welcomeLabel->setFont(QFont("Arial", 15));
welcomeLabel->setObjectName("WelcomeLabel");
// JobPathEditor dock
jobEditor = new JobPathEditor(this);
viewMgr->jobEditor = jobEditor;
jobDock = new QDockWidget(tr("Job Editor"), this);
jobDock->setAllowedAreas(Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea);
jobDock->setWidget(jobEditor);
jobDock->installEventFilter(this); // NOTE: see MainWindow::eventFilter() below
addDockWidget(Qt::RightDockWidgetArea, jobDock);
ui->menuView->addAction(jobDock->toggleViewAction());
connect(jobDock->toggleViewAction(), &QAction::triggered, jobEditor, &JobPathEditor::show);
#ifdef ENABLE_BACKGROUND_MANAGER
// Background Errors dock
BackgroundResultPanel *resPanel = new BackgroundResultPanel(this);
resPanelDock = new QDockWidget(tr("Errors"), this);
resPanelDock->setAllowedAreas(Qt::TopDockWidgetArea | Qt::BottomDockWidgetArea);
resPanelDock->setWidget(resPanel);
resPanelDock->installEventFilter(this); // NOTE: see eventFilter() below
addDockWidget(Qt::BottomDockWidgetArea, resPanelDock);
ui->menuView->addAction(resPanelDock->toggleViewAction());
ui->mainToolBar->addAction(resPanelDock->toggleViewAction());
// Add checkers FIXME: move to session?
JobCrossingChecker *jobCrossingChecker = new JobCrossingChecker(Session->m_Db, this);
Session->getBackgroundManager()->addChecker(jobCrossingChecker);
RsCheckerManager *rsChecker = new RsCheckerManager(Session->m_Db, this);
Session->getBackgroundManager()->addChecker(rsChecker);
#endif // ENABLE_BACKGROUND_MANAGER
// Allow JobPathEditor to use all vertical space when RsErrorWidget dock is at bottom
setCorner(Qt::BottomRightCorner, Qt::RightDockWidgetArea);
setCorner(Qt::BottomLeftCorner, Qt::LeftDockWidgetArea);
// Search Box
SearchResultModel *searchModel = new SearchResultModel(Session->m_Db, this);
searchEdit = new CustomCompletionLineEdit(searchModel, this);
searchEdit->setMinimumWidth(300);
searchEdit->setMinimumHeight(25);
searchEdit->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
searchEdit->setPlaceholderText(tr("Find"));
searchEdit->setClearButtonEnabled(true);
connect(searchEdit, &CustomCompletionLineEdit::completionDone, this,
&MainWindow::onJobSearchItemSelected);
connect(searchModel, &SearchResultModel::resultsReady, this,
&MainWindow::onJobSearchResultsReady);
QWidget *spacer = new QWidget();
spacer->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
ui->mainToolBar->addWidget(spacer);
ui->mainToolBar->addWidget(searchEdit);
setup_actions();
setCentralWidgetMode(CentralWidgetMode::StartPageMode);
QMenu *recentFilesMenu = new QMenu(this);
for (int i = 0; i < MaxRecentFiles; i++)
{
recentFileActs[i] = new QAction(this);
recentFileActs[i]->setVisible(false);
connect(recentFileActs[i], &QAction::triggered, this, &MainWindow::onOpenRecent);
recentFilesMenu->addAction(recentFileActs[i]);
}
updateRecentFileActions();
ui->actionOpen_Recent->setMenu(recentFilesMenu);
// Listen to changes to display welcomeLabel or view
connect(Session, &MeetingSession::segmentAdded, this, &MainWindow::checkLineNumber);
connect(Session, &MeetingSession::segmentRemoved, this, &MainWindow::checkLineNumber);
connect(Session, &MeetingSession::lineAdded, this, &MainWindow::checkLineNumber);
connect(Session, &MeetingSession::lineRemoved, this, &MainWindow::checkLineNumber);
}
MainWindow::~MainWindow()
{
Session->getViewManager()->m_mainWidget = nullptr;
stopCloseTimer();
delete ui;
}
void MainWindow::setup_actions()
{
databaseActionGroup = new QActionGroup(this);
databaseActionGroup->addAction(ui->actionAddJob);
databaseActionGroup->addAction(ui->actionRemoveJob);
databaseActionGroup->addAction(ui->actionStations);
databaseActionGroup->addAction(ui->actionRollingstockManager);
databaseActionGroup->addAction(ui->actionJob_Shifts);
databaseActionGroup->addAction(ui->action_JobsMgr);
databaseActionGroup->addAction(ui->actionRS_Session_Viewer);
databaseActionGroup->addAction(ui->actionMeeting_Information);
databaseActionGroup->addAction(ui->actionQuery);
databaseActionGroup->addAction(ui->actionClose);
databaseActionGroup->addAction(ui->actionPrint);
databaseActionGroup->addAction(ui->actionSave);
databaseActionGroup->addAction(ui->actionSaveCopy_As);
databaseActionGroup->addAction(ui->actionExport_PDF);
databaseActionGroup->addAction(ui->actionExport_Svg);
databaseActionGroup->addAction(ui->actionPrev_Job_Segment);
databaseActionGroup->addAction(ui->actionNext_Job_Segment);
connect(ui->actionOpen, &QAction::triggered, this, &MainWindow::onOpen);
connect(ui->actionNew, &QAction::triggered, this, &MainWindow::onNew);
connect(ui->actionClose, &QAction::triggered, this, &MainWindow::onCloseSession);
connect(ui->actionSave, &QAction::triggered, this, &MainWindow::onSave);
connect(ui->actionSaveCopy_As, &QAction::triggered, this, &MainWindow::onSaveCopyAs);
connect(ui->actionPrint, &QAction::triggered, this, &MainWindow::onPrint);
connect(ui->actionExport_PDF, &QAction::triggered, this, &MainWindow::onPrintPDF);
connect(ui->actionExport_Svg, &QAction::triggered, this, &MainWindow::onExportSvg);
connect(ui->actionProperties, &QAction::triggered, this, &MainWindow::onProperties);
connect(ui->actionStations, &QAction::triggered, this, &MainWindow::onStationManager);
connect(ui->actionRollingstockManager, &QAction::triggered, this,
&MainWindow::onRollingStockManager);
connect(ui->actionJob_Shifts, &QAction::triggered, this, &MainWindow::onShiftManager);
connect(ui->action_JobsMgr, &QAction::triggered, this, &MainWindow::onJobsManager);
connect(ui->actionRS_Session_Viewer, &QAction::triggered, this, &MainWindow::onSessionRSViewer);
connect(ui->actionMeeting_Information, &QAction::triggered, this,
&MainWindow::onMeetingInformation);
connect(ui->actionAddJob, &QAction::triggered, this, &MainWindow::onAddJob);
connect(ui->actionRemoveJob, &QAction::triggered, this, &MainWindow::onRemoveJob);
connect(ui->actionAbout, &QAction::triggered, this, &MainWindow::about);
connect(ui->actionAbout_Qt, &QAction::triggered, qApp, &QApplication::aboutQt);
#ifdef ENABLE_USER_QUERY
connect(ui->actionQuery, &QAction::triggered, this, &MainWindow::onExecQuery);
#else
ui->actionQuery->setVisible(false);
ui->actionQuery->setEnabled(false);
#endif
connect(ui->actionSettings, &QAction::triggered, this, &MainWindow::onOpenSettings);
connect(ui->actionExit, &QAction::triggered, this, &MainWindow::close);
ui->actionNext_Job_Segment->setToolTip(
tr("Hold shift and click to go to <b>last</b> job stop."));
ui->actionPrev_Job_Segment->setToolTip(
tr("Hold shift and click to go to <b>first</b> job stop."));
connect(ui->actionNext_Job_Segment, &QAction::triggered, this,
[]()
{
bool shiftPressed =
QGuiApplication::keyboardModifiers().testFlag(Qt::ShiftModifier);
Session->getViewManager()->requestJobShowPrevNextSegment(false, shiftPressed);
});
connect(ui->actionPrev_Job_Segment, &QAction::triggered, this,
[]()
{
bool shiftPressed =
QGuiApplication::keyboardModifiers().testFlag(Qt::ShiftModifier);
Session->getViewManager()->requestJobShowPrevNextSegment(true, shiftPressed);
});
}
void MainWindow::about()
{
OwningQPointer<QMessageBox> msgBox = new QMessageBox(this);
msgBox->setIcon(QMessageBox::Information);
msgBox->setWindowTitle(tr("About %1").arg(qApp->applicationDisplayName()));
const QString translatedText =
tr("<h3>%1</h3>"
"<p>This program makes it easier to deal with timetables and trains.</p>"
"<p>Version: <b>%2</b></p>"
"<p>Built: %3</p>"
"<p>Website: <a href='%4'>%4</a></p>")
.arg(qApp->applicationDisplayName(), qApp->applicationVersion(),
QDate::fromString(AppBuildDate, QLatin1String("MMM dd yyyy")).toString("dd/MM/yyyy"),
AppProjectWebSite);
msgBox->setTextFormat(Qt::RichText);
msgBox->setText(translatedText);
msgBox->setStandardButtons(QMessageBox::Ok);
msgBox->exec();
}
void MainWindow::onOpen()
{
DEBUG_ENTRY;
#ifdef SEARCHBOX_MODE_ASYNC
emit Session->getBackgroundManager()->abortTrivialTasks();
#endif
#ifdef ENABLE_BACKGROUND_MANAGER
if (Session->getBackgroundManager()->isRunning())
{
int ret = QMessageBox::warning(
this, tr("Backgroung Task"),
tr("Background task for checking rollingstock errors is still running.\n"
"Do you want to cancel it?"),
QMessageBox::Yes, QMessageBox::No, QMessageBox::Yes);
if (ret == QMessageBox::Yes)
Session->getBackgroundManager()->abortAllTasks();
else
return;
}
#endif
OwningQPointer<QFileDialog> dlg = new QFileDialog(this, tr("Open Session"));
dlg->setFileMode(QFileDialog::ExistingFile);
dlg->setAcceptMode(QFileDialog::AcceptOpen);
dlg->setDirectory(RecentDirStore::getDir(directory_key::session, RecentDirStore::Documents));
QStringList filters;
filters << FileFormats::tr(FileFormats::tttFormat);
filters << FileFormats::tr(FileFormats::sqliteFormat);
filters << FileFormats::tr(FileFormats::allFiles);
dlg->setNameFilters(filters);
if (dlg->exec() != QDialog::Accepted || !dlg)
return;
QString fileName = dlg->selectedUrls().value(0).toLocalFile();
if (fileName.isEmpty())
return;
RecentDirStore::setPath(directory_key::session, fileName);
QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));
if (!QThreadPool::globalInstance()->waitForDone(2000))
{
QMessageBox::warning(this, tr("Background Tasks"),
tr("Some background tasks are still running.\n"
"The file was not opened. Try again."));
QApplication::restoreOverrideCursor();
return;
}
QApplication::restoreOverrideCursor();
loadFile(fileName);
}
void MainWindow::loadFile(const QString &fileName)
{
DEBUG_ENTRY;
if (fileName.isEmpty())
return;
qDebug() << "Loading:" << fileName;
QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));
DB_Error err = Session->openDB(fileName, false);
QApplication::restoreOverrideCursor();
if (err == DB_Error::FormatTooOld)
{
int but = QMessageBox::warning(
this, tr("Version is old"),
tr("This file was created by an older version of %1.\n"
"Opening it without conversion might not work and even crash the application.\n"
"Do you want to open it anyway?")
.arg(qApp->applicationDisplayName()),
QMessageBox::Yes | QMessageBox::No, QMessageBox::No);
if (but == QMessageBox::Yes)
err = Session->openDB(fileName, true);
}
else if (err == DB_Error::FormatTooNew)
{
if (err == DB_Error::FormatTooOld)
{
int but = QMessageBox::warning(this, tr("Version is too new"),
tr("This file was created by a newer version of %1.\n"
"You should update the application first. Opening "
"this file might not work or even crash.\n"
"Do you want to open it anyway?")
.arg(qApp->applicationDisplayName()),
QMessageBox::Yes | QMessageBox::No, QMessageBox::No);
if (but == QMessageBox::Yes)
err = Session->openDB(fileName, true);
}
}
if (err == DB_Error::DbBusyWhenClosing)
showCloseWarning();
if (err != DB_Error::NoError)
return;
setCurrentFile(fileName);
// Fake we are coming from Start Page
// Otherwise we cannot show the first line
m_mode = CentralWidgetMode::StartPageMode;
checkLineNumber();
if (!Session->checkImportRSTablesEmpty())
{
// Probably the application crashed before finishing RS importation
// Give user choice to resume it or discard
OwningQPointer<QMessageBox> msgBox =
new QMessageBox(QMessageBox::Warning, tr("RS Import"),
tr("There is some rollingstock import data left in this file. "
"Probably the application has crashed!<br>"
"Before deleting it would you like to resume importation?<br>"
"<i>(Sorry for the crash, would you like to contact me and share "
"information about it?)</i>"),
QMessageBox::NoButton, this);
auto resumeBut = msgBox->addButton(tr("Resume importation"), QMessageBox::YesRole);
msgBox->addButton(tr("Just delete it"), QMessageBox::NoRole);
msgBox->setDefaultButton(resumeBut);
msgBox->setTextFormat(Qt::RichText);
msgBox->exec();
if (!msgBox)
return;
if (msgBox->clickedButton() == resumeBut)
{
Session->getViewManager()->resumeRSImportation();
}
else
{
Session->clearImportRSTables();
}
}
}
void MainWindow::setCurrentFile(const QString &fileName)
{
DEBUG_ENTRY;
if (fileName.isEmpty())
{
setWindowFilePath(QString()); // Reset title bar
return;
}
// Qt automatically takes care of showing stripped filename in window title
setWindowFilePath(fileName);
QStringList files = AppSettings.getRecentFiles();
files.removeAll(fileName);
files.prepend(fileName);
while (files.size() > MaxRecentFiles)
files.removeLast();
AppSettings.setRecentFiles(files);
updateRecentFileActions();
}
QString MainWindow::strippedName(const QString &fullFileName, bool *ok)
{
QFileInfo fi(fullFileName);
if (ok)
*ok = fi.exists();
return fi.fileName();
}
void MainWindow::updateRecentFileActions()
{
DEBUG_ENTRY;
QStringList files = AppSettings.getRecentFiles();
int numRecentFiles = qMin(files.size(), int(MaxRecentFiles));
for (int i = 0; i < numRecentFiles; i++)
{
bool ok = true;
QString name = strippedName(files[i], &ok);
if (name.isEmpty() || !ok)
{
files.removeAt(i);
i--;
numRecentFiles = qMin(files.size(), int(MaxRecentFiles));
}
else
{
QString text = tr("&%1 %2").arg(i + 1).arg(name);
recentFileActs[i]->setText(text);
recentFileActs[i]->setData(files[i]);
recentFileActs[i]->setToolTip(files[i]);
recentFileActs[i]->setVisible(true);
}
}
for (int j = numRecentFiles; j < MaxRecentFiles; ++j)
recentFileActs[j]->setVisible(false);
AppSettings.setRecentFiles(files);
}
void MainWindow::onOpenRecent()
{
DEBUG_ENTRY;
QAction *act = qobject_cast<QAction *>(sender());
if (!act)
return;
loadFile(act->data().toString());
}
void MainWindow::onNew()
{
DEBUG_ENTRY;
#ifdef SEARCHBOX_MODE_ASYNC
emit Session->getBackgroundManager()->abortTrivialTasks();
#endif
#ifdef ENABLE_BACKGROUND_MANAGER
if (Session->getBackgroundManager()->isRunning())
{
int ret = QMessageBox::warning(
this, tr("Backgroung Task"),
tr("Background task for checking rollingstock errors is still running.\n"
"Do you want to cancel it?"),
QMessageBox::Yes, QMessageBox::No, QMessageBox::Yes);
if (ret == QMessageBox::Yes)
Session->getBackgroundManager()->abortAllTasks();
else
return;
}
#endif // ENABLE_BACKGROUND_MANAGER
OwningQPointer<QFileDialog> dlg = new QFileDialog(this, tr("Create new Session"));
dlg->setFileMode(QFileDialog::AnyFile);
dlg->setAcceptMode(QFileDialog::AcceptSave);
dlg->setDirectory(RecentDirStore::getDir(directory_key::session, RecentDirStore::Documents));
QStringList filters;
filters << FileFormats::tr(FileFormats::tttFormat);
filters << FileFormats::tr(FileFormats::sqliteFormat);
filters << FileFormats::tr(FileFormats::allFiles);
dlg->setNameFilters(filters);
if (dlg->exec() != QDialog::Accepted || !dlg)
return;
QString fileName = dlg->selectedUrls().value(0).toLocalFile();
if (fileName.isEmpty())
return;
RecentDirStore::setPath(directory_key::session, fileName);
QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));
if (!QThreadPool::globalInstance()->waitForDone(2000))
{
QMessageBox::warning(this, tr("Background Tasks"),
tr("Some background tasks are still running.\n"
"The new file was not created. Try again."));
QApplication::restoreOverrideCursor();
return;
}
QFile f(fileName);
if (f.exists())
f.remove();
DB_Error err = Session->createNewDB(fileName);
QApplication::restoreOverrideCursor();
if (err == DB_Error::DbBusyWhenClosing)
showCloseWarning();
if (err != DB_Error::NoError)
return;
setCurrentFile(fileName);
checkLineNumber();
}
void MainWindow::onSave()
{
if (!Session->getViewManager()->closeEditors())
return;
Session->releaseAllSavepoints();
}
void MainWindow::onSaveCopyAs()
{
DEBUG_ENTRY;
if (!Session->getViewManager()->closeEditors())
return;
OwningQPointer<QFileDialog> dlg = new QFileDialog(this, tr("Save Session Copy"));
dlg->setFileMode(QFileDialog::AnyFile);
dlg->setAcceptMode(QFileDialog::AcceptSave);
dlg->setDirectory(RecentDirStore::getDir(directory_key::session, RecentDirStore::Documents));
QStringList filters;
filters << FileFormats::tr(FileFormats::tttFormat);
filters << FileFormats::tr(FileFormats::sqliteFormat);
filters << FileFormats::tr(FileFormats::allFiles);
dlg->setNameFilters(filters);
if (dlg->exec() != QDialog::Accepted || !dlg)
return;
QString fileName = dlg->selectedUrls().value(0).toLocalFile();
if (fileName.isEmpty())
return;
RecentDirStore::setPath(directory_key::session, fileName);
QFile f(fileName);
if (f.exists())
f.remove();
database backupDB(fileName.toUtf8(), SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE);
int rc = Session->m_Db.backup(backupDB,
[](int pageCount, int remaining, int res)
{
Q_UNUSED(res)
qDebug() << pageCount << "/" << remaining;
});
if (rc != SQLITE_OK && rc != SQLITE_DONE)
{
QString errMsg = Session->m_Db.error_msg();
qDebug() << Session->m_Db.error_code() << errMsg;
QMessageBox::warning(this, tr("Error saving copy"), errMsg);
}
}
void MainWindow::closeEvent(QCloseEvent *e)
{
if (closeSession())
e->accept();
else
e->ignore();
}
void MainWindow::showCloseWarning()
{
QMessageBox::warning(this, tr("Error while Closing"),
tr("There was an error while closing the database.\n"
"Make sure there aren't any background tasks running and try again."));
}
void MainWindow::stopCloseTimer()
{
if (closeTimerId)
{
killTimer(closeTimerId);
closeTimerId = 0;
}
}
void MainWindow::setCentralWidgetMode(MainWindow::CentralWidgetMode mode)
{
switch (mode)
{
case CentralWidgetMode::StartPageMode:
{
jobDock->hide();
#ifdef ENABLE_BACKGROUND_MANAGER
resPanelDock->hide();
#endif // ENABLE_BACKGROUND_MANAGER
welcomeLabel->setText(tr("<p>Open a file: <b>File</b> > <b>Open</b></p>"
"<p>Create new project: <b>File</b> > <b>New</b></p>"));
statusBar()->showMessage(tr("Open file or create a new one"));
break;
}
case CentralWidgetMode::NoLinesWarningMode:
{
jobDock->show();
#ifdef ENABLE_BACKGROUND_MANAGER
resPanelDock->hide();
#endif // ENABLE_BACKGROUND_MANAGER
welcomeLabel->setText(
tr("<p><b>There are no lines in this session</b></p>"
"<p>"
"<table align=\"center\">"
"<tr>"
"<td>Start by creating the railway layout for this session:</td>"
"</tr>"
"<tr>"
"<td>"
"<table>"
"<tr>"
"<td>1.</td>"
"<td>Create stations (<b>Edit</b> > <b>Stations</b>)</td>"
"</tr>"
"<tr>"
"<td>2.</td>"
"<td>Create railway lines (<b>Edit</b> > <b>Stations</b> > <b>Lines Tab</b>)</td>"
"</tr>"
"<tr>"
"<td>3.</td>"
"<td>Add stations to railway lines</td>"
"</tr>"
"<tr>"
"<td></td>"
"<td>(<b>Edit</b> > <b>Stations</b> > <b>Lines Tab</b> > <b>Edit Line</b>)</td>"
"</tr>"
"</table>"
"</td>"
"</tr>"
"</table>"
"</p>"));
break;
}
case CentralWidgetMode::ViewSessionMode:
{
jobDock->show();
#ifdef ENABLE_BACKGROUND_MANAGER
resPanelDock->show();
#endif // ENABLE_BACKGROUND_MANAGER
welcomeLabel->setText(QString());
break;
}
}
enableDBActions(mode != CentralWidgetMode::StartPageMode);
if (mode == CentralWidgetMode::ViewSessionMode)
{
if (centralWidget() != view)
{
takeCentralWidget(); // Remove ownership from welcomeLabel
setCentralWidget(view);
view->show();
welcomeLabel->hide();
}
// Enable Job Creation
ui->actionAddJob->setEnabled(true);
ui->actionAddJob->setToolTip(tr("Add train job"));
// Update actions based on Job selection
JobStopEntry selectedJob =
Session->getViewManager()->getLineGraphMgr()->getCurrentSelectedJob();
onJobSelected(selectedJob.jobId);
}
else
{
if (centralWidget() != welcomeLabel)
{
takeCentralWidget(); // Remove ownership from LineGraphWidget
setCentralWidget(welcomeLabel);
view->hide();
welcomeLabel->show();
}
// If there aren't lines prevent from creating jobs
ui->actionAddJob->setEnabled(false);
ui->actionAddJob->setToolTip(
tr("You must create at least one railway segment before adding job to this session"));
ui->actionRemoveJob->setEnabled(false);
}
m_mode = mode;
}
void MainWindow::onCloseSession()
{
closeSession();
}
void MainWindow::onProperties()
{
OwningQPointer<PropertiesDialog> dlg = new PropertiesDialog(this);
dlg->exec();
}
void MainWindow::onMeetingInformation()
{
OwningQPointer<MeetingInformationDialog> dlg = new MeetingInformationDialog(this);
int ret = dlg->exec();
if (dlg && ret == QDialog::Accepted)
dlg->saveData();
}
bool MainWindow::closeSession()
{
DB_Error err = Session->closeDB();
if (err == DB_Error::DbBusyWhenClosing)
{
if (closeTimerId)
{
// We already tried again
stopCloseTimer();
showCloseWarning();
return false;
}
// Start a timer to try again
closeTimerId = startTimer(1500);
return false;
}
stopCloseTimer();
if (err != DB_Error::NoError && err != DB_Error::DbNotOpen)
return false;
setCentralWidgetMode(CentralWidgetMode::StartPageMode);
// Reset filePath to refresh title
setCurrentFile(QString());
return true;
}
void MainWindow::enableDBActions(bool enable)
{
databaseActionGroup->setEnabled(enable);
searchEdit->setEnabled(enable);
if (!enable)
jobEditor->setEnabled(false);
#ifdef ENABLE_BACKGROUND_MANAGER
resPanelDock->widget()->setEnabled(enable);
#endif
}
void MainWindow::onStationManager()
{
Session->getViewManager()->showStationsManager();
}
void MainWindow::onRollingStockManager()
{
Session->getViewManager()->showRSManager();
}
void MainWindow::onShiftManager()
{
Session->getViewManager()->showShiftManager();
}
void MainWindow::onJobsManager()
{
Session->getViewManager()->showJobsManager();
}
void MainWindow::onAddJob()
{
Session->getViewManager()->requestJobCreation();
}
void MainWindow::onRemoveJob()
{
DEBUG_ENTRY;
Session->getViewManager()->removeSelectedJob();
}
void MainWindow::onPrint()
{
OwningQPointer<PrintWizard> wizard = new PrintWizard(Session->m_Db, this);
wizard->setOutputType(Print::OutputType::Native);
wizard->exec();
}
void MainWindow::onPrintPDF()
{
OwningQPointer<PrintWizard> wizard = new PrintWizard(Session->m_Db, this);
wizard->setOutputType(Print::OutputType::Pdf);
wizard->exec();
}
void MainWindow::onExportSvg()
{
OwningQPointer<PrintWizard> wizard = new PrintWizard(Session->m_Db, this);
wizard->setOutputType(Print::OutputType::Svg);
wizard->exec();
}
#ifdef ENABLE_USER_QUERY
void MainWindow::onExecQuery()
{
DEBUG_ENTRY;
SQLConsole *console = new SQLConsole(this);
console->setAttribute(Qt::WA_DeleteOnClose);
console->show();
}
#endif
void MainWindow::onOpenSettings()
{
DEBUG_ENTRY;
OwningQPointer<SettingsDialog> dlg = new SettingsDialog(this);
dlg->loadSettings();
dlg->exec();
}
void MainWindow::checkLineNumber()
{
RailwaySegmentHelper helper(Session->m_Db);
bool isLine = false;
db_id graphObjId = 0;
if (!helper.findFirstLineOrSegment(graphObjId, isLine))
graphObjId = 0;
if (graphObjId && m_mode != CentralWidgetMode::ViewSessionMode)
{
// First line was added or newly opened file -> Session has at least one line
setCentralWidgetMode(CentralWidgetMode::ViewSessionMode);
// Load first line or segment
view->tryLoadGraph(graphObjId,
isLine ? LineGraphType::RailwayLine : LineGraphType::RailwaySegment);
}
else if (graphObjId == 0 && m_mode != CentralWidgetMode::NoLinesWarningMode)
{
// Last line removed -> Session has no line
setCentralWidgetMode(CentralWidgetMode::NoLinesWarningMode);
}
}
void MainWindow::timerEvent(QTimerEvent *e)
{
if (e->timerId() == closeTimerId)
{
closeSession();
return;
}
QMainWindow::timerEvent(e);
}
void MainWindow::onJobSelected(db_id jobId)
{
const bool selected = jobId != 0;
ui->actionPrev_Job_Segment->setEnabled(selected);
ui->actionNext_Job_Segment->setEnabled(selected);
ui->actionRemoveJob->setEnabled(selected);
QString removeJobTooltip;
if (selected)
removeJobTooltip = tr("Remove selected Job");
else
removeJobTooltip = tr("First select a Job by double click on graph or type in search box");
ui->actionRemoveJob->setToolTip(removeJobTooltip);
}
// QT-BUG 69922: If user closes a floating dock widget, when shown again it cannot dock anymore
// HACK: intercept dock close event and manually re-dock and hide so next time is shown it's docked
// NOTE: calling directly 'QDockWidget::setFloating(false)' from inside 'eventFinter()' causes CRASH
// so queue it. Cannot use 'QMetaObject::invokeMethod()' because it's not a slot.
bool MainWindow::eventFilter(QObject *watched, QEvent *event)
{
if (watched == jobDock && event->type() == QEvent::Close)
{
if (jobDock->isFloating())
{
QTimer::singleShot(0, jobDock, [this]() { jobDock->setFloating(false); });
}
}
#ifdef ENABLE_BACKGROUND_MANAGER
else if (watched == resPanelDock && event->type() == QEvent::Close)
{
if (resPanelDock->isFloating())
{
QTimer::singleShot(0, resPanelDock, [this]() { resPanelDock->setFloating(false); });
}
}
#endif // ENABLE_BACKGROUND_MANAGER
return QMainWindow::eventFilter(watched, event);
}
void MainWindow::onSessionRSViewer()
{
Session->getViewManager()->showSessionStartEndRSViewer();
}
void MainWindow::onJobSearchItemSelected()
{
db_id jobId = 0;
QString tmp;
if (!searchEdit->getData(jobId, tmp))
return;
searchEdit->clear(); // Clear text
Session->getViewManager()->requestJobSelection(jobId, true, true);
}
void MainWindow::onJobSearchResultsReady()
{
searchEdit->resizeColumnToContents();
searchEdit->selectFirstIndexOrNone(true);
}