23 #include "application.h"
24 #include "apprunner.h"
25 #include "commandline.h"
26 #include "configuration/doomseekerconfig.h"
27 #include "configuration/queryspeed.h"
28 #include "connectionhandler.h"
29 #include "customservers.h"
30 #include "datapaths.h"
31 #include "doomseekerfilepaths.h"
32 #include "fileutils.h"
34 #include "gui/aboutdialog.h"
35 #include "gui/checkwadsdlg.h"
36 #include "gui/configuration/doomseekerconfigurationdialog.h"
37 #include "gui/configuration/irc/ircconfigurationdialog.h"
38 #include "gui/copytextdlg.h"
39 #include "gui/createserverdialog.h"
40 #include "gui/demomanager.h"
41 #include "gui/dockBuddiesList.h"
42 #include "gui/freedoomdialog.h"
43 #include "gui/helpers/playersdiagram.h"
44 #include "gui/helpers/taskbarbutton.h"
45 #include "gui/helpers/taskbarprogress.h"
46 #include "gui/ip2cupdatebox.h"
47 #include "gui/irc/ircdock.h"
48 #include "gui/irc/ircsounds.h"
49 #include "gui/logdock.h"
50 #include "gui/mainwindow.h"
51 #include "gui/models/serverlistmodel.h"
52 #include "gui/programargshelpdialog.h"
53 #include "gui/serverdetailsdock.h"
54 #include "gui/serverfilterdock.h"
55 #include "gui/serverlist.h"
56 #include "gui/wadseekerinterface.h"
57 #include "gui/wadseekershow.h"
58 #include "gui/widgets/serversstatuswidget.h"
59 #include "ip2c/ip2cloader.h"
60 #include "irc/configuration/chatnetworkscfg.h"
61 #include "irc/configuration/ircconfig.h"
62 #include "joincommandlinebuilder.h"
65 #include "pathfinder/pathfinder.h"
66 #include "pathfinder/wadpathfinder.h"
67 #include "plugins/engineplugin.h"
68 #include "plugins/pluginloader.h"
69 #include "refresher/refresher.h"
70 #include "serverapi/broadcast.h"
71 #include "serverapi/broadcastmanager.h"
72 #include "serverapi/gameclientrunner.h"
73 #include "serverapi/mastermanager.h"
74 #include "serverapi/message.h"
75 #include "serverapi/server.h"
76 #include "serverapi/serverlistcounttracker.h"
77 #include "strings.hpp"
78 #include "ui_mainwindow.h"
79 #include "updater/autoupdater.h"
80 #include "updater/updatechannel.h"
81 #include "updater/updateinstaller.h"
82 #include "updater/updatepackage.h"
83 #include "wadseeker/entities/checksum.h"
86 #include <QApplication>
87 #include <QDesktopServices>
88 #include <QDockWidget>
90 #include <QHeaderView>
93 #include <QMessageBox>
94 #include <QMessageBox>
96 #include <QProgressBar>
97 #include <QSizePolicy>
100 const QString MainWindow::HELP_SITE_URL =
"https://doomseeker.drdteam.org/help";
109 class QueryMenuAction :
public QAction
115 this->pPlugin = plugin;
117 if (plugin !=
nullptr)
119 connect(
this, SIGNAL(toggled(
bool)), plugin->data()->masterClient,
120 SLOT(setEnabled(
bool)));
121 connect(
this, SIGNAL(toggled(
bool)), statusWidget, SLOT(setMasterEnabledStatus(
bool)));
134 DClass<MainWindow> :
public Ui::MainWindowWnd
137 PrivData() : bTotalRefreshInProcess(false), buddiesList(nullptr),
138 bWasMaximized(false), bWantToQuit(false), logDock(nullptr),
139 masterManager(nullptr), trayIcon(nullptr), trayIconMenu(nullptr)
143 QApplication *application;
144 QTimer autoRefreshTimer;
147 QWidget *autoUpdaterStatusBarWidget;
148 QPushButton *autoUpdaterAbortButton;
149 QLabel *autoUpdaterLabel;
150 QProgressBar *autoUpdaterFileProgressBar;
151 QProgressBar *autoUpdaterOverallProgressBar;
157 bool bTotalRefreshInProcess;
175 QProgressBar *ip2cUpdateProgressBar;
179 QPointer<FreedoomDialog> freedoomDialog;
184 QHash<const EnginePlugin *, QueryMenuAction *> queryMenuPorts;
185 QHash<const EnginePlugin *, ServersStatusWidget *> serversStatusesWidgets;
186 QAction *toolBarGetServers;
187 QSystemTrayIcon *trayIcon;
191 int updaterInstallerErrorCode;
194 QDockWidget *mainDock;
204 d->autoUpdater =
nullptr;
205 d->mainDock =
nullptr;
206 d->connectionHandler =
nullptr;
208 d->updaterInstallerErrorCode = 0;
210 d->application = application;
212 this->setAttribute(Qt::WA_DeleteOnClose,
true);
216 d->taskbarProgress = d->taskbarButton->progress();
220 initAutoUpdaterWidgets();
222 d->updatesConfirmationWidget->hide();
223 d->updatesDownloadedWidget->hide();
226 #ifndef WITH_AUTOUPDATES
227 d->menuActionCheckForUpdates->setVisible(
false);
234 d->menuView->addAction(d->buddiesList->toggleViewAction());
235 d->buddiesList->toggleViewAction()->setText(MainWindow::tr(
"&Buddies"));
236 d->buddiesList->toggleViewAction()->setShortcut(MainWindow::tr(
"CTRL+B"));
238 connect(d->buddiesList, SIGNAL(joinServer(ServerPtr)),
this, SLOT(runGame(ServerPtr)));
239 d->buddiesList->hide();
240 this->addDockWidget(Qt::LeftDockWidgetArea, d->buddiesList);
243 initServerFilterDock();
245 splitDockWidget(d->mainDock, d->serverFilterDock, Qt::Horizontal);
248 d->serverList =
new ServerList(d->tableServers,
this);
252 d->serverList->connect(d->broadcastManager,
253 SIGNAL(newServerDetected(ServerPtr,
int)), SLOT(registerServer(ServerPtr)));
254 d->serverList->connect(d->broadcastManager,
255 SIGNAL(serverLost(ServerPtr)), SLOT(removeServer(ServerPtr)));
257 initServerDetailsDock();
258 tabifyDockWidget(d->ircDock, d->detailsDock);
261 d->menuActionRecordDemo->setChecked(gConfig.doomseeker.bRecordDemo);
265 d->masterManager->setBroadcastManager(d->broadcastManager);
266 d->buddiesList->scan(d->masterManager);
267 connect(d->masterManager, SIGNAL(masterMessage(
MasterClient*,
const QString&,
const QString&,
bool)),
268 this, SLOT(masterManagerMessages(
MasterClient*,
const QString&,
const QString&,
bool)));
273 fillQueryMenu(d->masterManager);
276 QList<ServerPtr> customServers = d->masterManager->customServs()->readConfig();
277 for (ServerPtr server : customServers)
279 d->serverList->registerServer(server);
282 setWindowIcon(Application::icon());
285 initAutoRefreshTimer();
286 connect(&d->autoRefreshTimer, SIGNAL(timeout()),
this, SLOT(autoRefreshTimer_timeout()));
297 d->menuActionUpdateIP2C->setEnabled(
false);
300 d->ip2cLoader->load();
302 restoreState(QByteArray::fromBase64(gConfig.doomseeker.mainWindowState.toUtf8()));
303 restoreGeometry(gConfig.doomseeker.mainWindowGeometry);
307 QTimer::singleShot(1,
this, SLOT(postInitAppStartup()));
310 MainWindow::~MainWindow()
313 gConfig.doomseeker.mainWindowGeometry = saveGeometry();
314 gConfig.doomseeker.mainWindowState = saveState().toBase64();
316 if (d->updateChannelOnUpdateStart !=
nullptr)
317 delete d->updateChannelOnUpdateStart;
318 if (d->autoUpdater !=
nullptr)
320 d->autoUpdater->disconnect();
321 delete d->autoUpdater;
323 if (d->connectionHandler)
324 delete d->connectionHandler;
326 QList<QAction *> menuQueryActions = d->menuQuery->actions();
327 QList<QAction *>::iterator it;
328 for (QAction *action : menuQueryActions)
330 QString pluginName = action->text();
332 if (!pluginName.isEmpty())
334 IniSection pluginConfig = gConfig.iniSectionForPlugin(pluginName);
335 pluginConfig[
"Query"] = action->isChecked();
339 if (d->trayIcon !=
nullptr)
341 d->trayIcon->setVisible(
false);
343 d->trayIcon =
nullptr;
346 if (d->trayIconMenu !=
nullptr)
348 delete d->trayIconMenu;
349 d->trayIconMenu =
nullptr;
352 delete d->serverList;
354 if (d->masterManager !=
nullptr)
355 delete d->masterManager;
357 if (d->ip2cLoader !=
nullptr)
358 delete d->ip2cLoader;
361 void MainWindow::abortAutoUpdater()
363 if (d->autoUpdater !=
nullptr)
364 d->autoUpdater->abort();
367 void MainWindow::autoRefreshTimer_timeout()
369 if (gConfig.doomseeker.bQueryAutoRefreshDontIfActive && !isMinimized())
371 if (QApplication::activeWindow() !=
nullptr)
378 void MainWindow::blockRefreshButtons()
380 d->toolBarGetServers->setEnabled(
false);
385 return d->buddiesList;
388 void MainWindow::changeEvent(QEvent *event)
390 if (event->type() == QEvent::ActivationChange && isActiveWindow() && !isMinimized() && !isHidden())
392 d->serverList->cleanUp();
395 QMainWindow::changeEvent(event);
398 void MainWindow::checkForUpdates(
bool bUserTriggered)
400 if (d->autoUpdater !=
nullptr)
402 if (d->autoUpdater->isRunning())
404 QMessageBox::warning(
this, tr(
"Doomseeker - Auto Update"),
405 tr(
"Update is already in progress."));
410 delete d->autoUpdater;
411 d->autoUpdater =
nullptr;
414 gLog << tr(
"Removing old update packages from local temporary space.");
415 QStringList removeFilter(QString(
"%1*").arg(DataPaths::UPDATE_PACKAGE_FILENAME_PREFIX));
419 showAndLogStatusMessage(tr(
"Checking for updates..."));
421 this->connect(d->autoUpdater, SIGNAL(statusMessage(QString)),
422 SLOT(showAndLogStatusMessage(QString)));
423 this->connect(d->autoUpdater, SIGNAL(finished()),
424 SLOT(onAutoUpdaterFinish()));
425 this->connect(d->autoUpdater, SIGNAL(downloadAndInstallConfirmationRequested()),
426 SLOT(onAutoUpdaterDownloadAndInstallConfirmationRequest()));
427 this->connect(d->autoUpdater, SIGNAL(overallProgress(
int,
int,
const QString&)),
428 SLOT(onAutoUpdaterOverallProgress(
int,
int,
const QString&)));
429 this->connect(d->autoUpdater, SIGNAL(packageDownloadProgress(qint64,qint64)),
430 SLOT(onAutoUpdaterFileProgress(qint64,qint64)));
432 QMap<QString, QList<QString> > ignoredPackagesRevisions;
435 for (
const QString &package : gConfig.autoUpdates.lastKnownUpdateRevisions.keys())
437 QString revision = gConfig.autoUpdates.lastKnownUpdateRevisions[package];
440 ignoredPackagesRevisions.insert(package, list);
443 d->autoUpdater->setIgnoreRevisions(ignoredPackagesRevisions);
446 d->autoUpdater->setChannel(channel);
447 *d->updateChannelOnUpdateStart = channel;
449 bool bRequireConfirmation =
true;
452 bRequireConfirmation = (gConfig.autoUpdates.updateMode
455 d->autoUpdater->setRequireDownloadAndInstallConfirmation(bRequireConfirmation);
456 d->autoUpdaterStatusBarWidget->show();
457 d->autoUpdater->start();
463 const bool bUserTriggered =
true;
464 checkForUpdates(!bUserTriggered);
469 const bool bUserTriggered =
true;
470 checkForUpdates(bUserTriggered);
473 void MainWindow::closeEvent(QCloseEvent *event)
478 if (d->trayIcon !=
nullptr && gConfig.doomseeker.bCloseToTrayIcon && !d->bWantToQuit)
480 d->bWasMaximized = isMaximized();
488 void MainWindow::confirmUpdateInstallation()
490 assert(d->autoUpdater !=
nullptr &&
"MainWindow::confirmUpdateInstallation()");
491 d->updatesConfirmationWidget->hide();
492 d->autoUpdater->confirmDownloadAndInstall();
495 void MainWindow::connectIP2CLoader()
497 this->connect(d->ip2cLoader, SIGNAL(finished()), SLOT(ip2cJobsFinished()));
498 this->connect(d->ip2cLoader, SIGNAL(downloadProgress(qint64,qint64)),
499 SLOT(ip2cDownloadProgress(qint64,qint64)));
502 void MainWindow::discardUpdates()
504 assert(d->autoUpdater !=
nullptr &&
"MainWindow::confirmUpdateInstallation()");
505 d->updatesConfirmationWidget->hide();
508 const QList<UpdatePackage> &pkgList = d->autoUpdater->newUpdatePackages();
511 gConfig.autoUpdates.lastKnownUpdateRevisions.insert(pkg.name, pkg.revision);
513 d->autoUpdater->abort();
519 connect(gRefresher, SIGNAL(block()),
this, SLOT(blockRefreshButtons()));
520 connect(gRefresher, SIGNAL(finishedQueryingMaster(
MasterClient*)),
this, SLOT(finishedQueryingMaster(
MasterClient*)));
521 connect(gRefresher, SIGNAL(sleepingModeEnter()),
this, SLOT(refreshThreadEndsWork()));
522 connect(gRefresher, SIGNAL(sleepingModeEnter()), d->buddiesList, SLOT(scan()));
523 connect(gRefresher, SIGNAL(sleepingModeExit()),
this, SLOT(refreshThreadBeginsWork()));
526 connect(d->menuActionAbout, SIGNAL(triggered()),
this, SLOT(menuHelpAbout()));
527 connect(d->menuActionAboutQt, SIGNAL(triggered()), d->application, SLOT(aboutQt()));
528 connect(d->menuActionBuddies, SIGNAL(triggered()),
this, SLOT(menuBuddies()));
529 connect(d->menuActionConfigure, SIGNAL(triggered()),
this, SLOT(menuOptionsConfigure()));
530 connect(d->menuActionCreateServer, SIGNAL(triggered()),
this, SLOT(menuCreateServer()));
531 connect(d->menuActionHelp, SIGNAL(triggered()),
this, SLOT (menuHelpHelp()));
532 connect(d->menuActionIRCOptions, SIGNAL(triggered()),
this, SLOT(menuIRCOptions()));
533 connect(d->menuActionLog, SIGNAL(triggered()),
this, SLOT(menuLog()));
534 connect(d->menuActionManageDemos, SIGNAL(triggered()),
this, SLOT(menuManageDemos()));
535 connect(d->menuActionRecordDemo, SIGNAL(triggered()),
this, SLOT(menuRecordDemo()));
536 connect(d->menuActionUpdateIP2C, SIGNAL(triggered()),
this, SLOT(menuUpdateIP2C()));
537 connect(d->menuActionQuit, SIGNAL(triggered()),
this, SLOT(quitProgram()));
538 connect(d->menuActionViewIRC, SIGNAL(triggered()),
this, SLOT(menuViewIRC()));
539 connect(d->menuActionWadseeker, SIGNAL(triggered()),
this, SLOT(menuWadSeeker()));
542 connect(d->serverFilterDock, SIGNAL(nonEmptyServerGroupingAtTopToggled(
bool)),
543 d->serverList, SLOT(setGroupServersWithPlayersAtTop(
bool)));
546 connect(d->serverList, SIGNAL(serverDoubleClicked(ServerPtr)),
this, SLOT(runGame(ServerPtr)));
547 connect(d->serverList, SIGNAL(displayServerJoinCommandLine(
const ServerPtr&)),
this, SLOT(showServerJoinCommandLine(
const ServerPtr&)));
548 connect(d->serverList, SIGNAL(findMissingWADs(
const ServerPtr&)),
this, SLOT(findMissingWADs(
const ServerPtr&)));
549 connect(d->serverList, SIGNAL(serverInfoUpdated(ServerPtr)),
this, SLOT(serverAddedToList(ServerPtr)));
550 connect(d->buddiesList, SIGNAL(scanCompleted()), d->serverList, SLOT(redraw()));
557 for (
unsigned i = 0; i < gPlugins->numPlugins(); ++i)
560 if (!plugin->data()->hasMasterClient() && !plugin->data()->hasBroadcast())
563 if (plugin->data()->hasMasterClient())
565 MasterClient *pMasterClient = plugin->data()->masterClient;
566 pMasterClient->updateAddress();
567 masterManager->addMaster(pMasterClient);
570 if (plugin->data()->hasBroadcast())
571 d->broadcastManager->registerPlugin(plugin);
575 d->serversStatusesWidgets.insert(plugin, statusWidget);
577 this->connect(statusWidget, SIGNAL(clicked(
const EnginePlugin*)),
579 this->connect(statusWidget, SIGNAL(counterUpdated()),
580 SLOT(updateRefreshProgress()));
582 statusBar()->addPermanentWidget(statusWidget);
584 QString name = gPlugins->info(i)->data()->name;
585 auto query =
new QueryMenuAction(plugin, statusWidget, d->menuQuery);
586 d->queryMenuPorts.insert(plugin, query);
588 d->menuQuery->addAction(query);
590 query->setCheckable(
true);
591 query->setIcon(plugin->icon());
592 query->setText(name);
594 IniSection pluginConfig = gConfig.iniSectionForPlugin(name);
598 bool enabled = pluginConfig[
"Query"];
610 void MainWindow::findMissingWADs(
const ServerPtr &server)
613 QList<PWad> wads = server->wads();
614 PathFinder pathFinder = server->wadPathFinder();
615 QList<PWad> missingWads;
616 QList<PWad> incompatibleWads;
619 checkWadsDlg->addWads(wads);
620 const CheckResult checkResults = checkWadsDlg->checkWads();
622 for (
const PWad &wad : checkResults.missingWads)
626 incompatibleWads << checkResults.incompatibleWads;
628 if (missingWads.isEmpty() && incompatibleWads.isEmpty())
630 QMessageBox::information(
this, tr(
"All WADs found"), tr(
"All of the WADs used by this server are present."));
635 dialog.setAllowIgnore(
false);
636 if (dialog.exec() == QDialog::Accepted && dialog.decision() == MissingWadsDialog::Install)
638 if (!gWadseekerShow->checkWadseekerValidity(
this))
641 wadseeker->setCustomSites(server->allWebSites());
642 wadseeker->
setWads(dialog.filesToDownload());
643 wadseeker->setAttribute(Qt::WA_DeleteOnClose);
651 updateMasterAddresses();
652 gRefresher->setDelayBetweenResends(gConfig.doomseeker.querySpeed().delayBetweenSingleServerAttempts);
655 if (configDialog.wasAppearanceChanged())
657 updateDynamicAppearance();
662 if (configDialog.isRestartNeeded())
664 QString warningRestartNeeded = tr(
"Doomseeker needs to be restarted for some changes to be applied.");
665 d->importantMessagesWidget->addMessage(warningRestartNeeded);
669 if (lookupHostsChanged)
670 d->serverList->lookupHosts();
673 if (configDialog.customServersChanged())
675 d->serverList->removeCustomServers();
676 QList<ServerPtr> servers = d->masterManager->customServs()->readConfig();
677 for (ServerPtr server : servers)
679 d->serverList->registerServer(server);
681 refreshCustomServers();
685 void MainWindow::finishedQueryingMaster(
MasterClient *master)
687 if (master ==
nullptr)
690 for (
int i = 0; i < master->numServers(); i++)
691 d->serverList->registerServer((*master)[i]);
694 void MainWindow::fixIconsDpi()
697 QIcon icon(
":/icons/exclamation_16.png");
698 d->lblExclamation1->setPixmap(icon.pixmap(16));
699 d->lblExclamation2->setPixmap(icon.pixmap(16));
702 void MainWindow::getServers()
707 QString message = tr(
"Doomseeker is unable to proceed with the refresh"
708 " operation because the following problem has occurred:\n\n");
710 if (gPlugins->numPlugins() == 0)
711 message += tr(
"Plugins are missing from the \"engines/\" directory.");
712 else if (!isAnyMasterEnabled())
713 message += tr(
"No master servers are enabled in the \"Query\" menu.");
715 message += tr(
"Unknown error occurred.");
718 QMessageBox::warning(
this, tr(
"Doomseeker - refresh problem"), message);
722 d->bTotalRefreshInProcess =
true;
723 d->autoRefreshTimer.stop();
724 gLog << tr(
"Total refresh initialized!");
729 d->serverList->removeNonSpecialServers();
730 refreshServersOnList();
732 if (!isAnyMasterEnabled() && !d->serverList->hasAtLeastOneServer())
734 gLog << tr(
"Warning: No master servers were enabled for this refresh. "
735 "Check your Query menu or \"engines/\" directory.");
738 d->masterManager->clearServers();
739 for (
int i = 0; i < d->masterManager->numMasters(); ++i)
744 gRefresher->registerMaster(pMaster);
748 bool MainWindow::hasCustomServers()
const
750 CustomServers *customServers = d->masterManager->customServs();
751 return customServers->numServers() > 0;
756 const unsigned MIN_DELAY = 30;
757 const unsigned MAX_DELAY = 3600;
759 bool bEnabled = gConfig.doomseeker.bQueryAutoRefreshEnabled;
762 d->autoRefreshTimer.stop();
767 unsigned &delay = gConfig.doomseeker.queryAutoRefreshEverySeconds;
770 if (delay < MIN_DELAY)
772 else if (delay > MAX_DELAY)
775 unsigned delayMs = delay * 1000;
777 d->autoRefreshTimer.setSingleShot(
false);
778 d->autoRefreshTimer.start(delayMs);
782 void MainWindow::initAutoUpdaterWidgets()
784 static const int FILE_BAR_WIDTH = 50;
785 static const int OVERALL_BAR_WIDTH = 180;
787 d->autoUpdaterStatusBarWidget =
new QWidget(statusBar());
788 d->autoUpdaterStatusBarWidget->setLayout(
new QHBoxLayout(d->autoUpdaterStatusBarWidget));
789 d->autoUpdaterStatusBarWidget->layout()->setContentsMargins(QMargins(0, 0, 0, 0));
790 statusBar()->addPermanentWidget(d->autoUpdaterStatusBarWidget);
791 d->autoUpdaterStatusBarWidget->hide();
793 d->autoUpdaterLabel =
new QLabel(d->autoUpdaterStatusBarWidget);
794 d->autoUpdaterLabel->setText(tr(
"Auto Updater:"));
795 d->autoUpdaterStatusBarWidget->layout()->addWidget(d->autoUpdaterLabel);
797 d->autoUpdaterFileProgressBar = mkStdProgressBarForStatusBar();
798 d->autoUpdaterFileProgressBar->setFormat(
"%p%");
799 d->autoUpdaterFileProgressBar->setMaximumWidth(FILE_BAR_WIDTH);
800 d->autoUpdaterFileProgressBar->setMinimumWidth(FILE_BAR_WIDTH);
801 d->autoUpdaterStatusBarWidget->layout()->addWidget(d->autoUpdaterFileProgressBar);
803 d->autoUpdaterOverallProgressBar = mkStdProgressBarForStatusBar();
804 d->autoUpdaterOverallProgressBar->setMaximumWidth(OVERALL_BAR_WIDTH);
805 d->autoUpdaterOverallProgressBar->setMinimumWidth(OVERALL_BAR_WIDTH);
806 d->autoUpdaterStatusBarWidget->layout()->addWidget(d->autoUpdaterOverallProgressBar);
808 d->autoUpdaterAbortButton =
new QPushButton(statusBar());
809 d->autoUpdaterAbortButton->setToolTip(tr(
"Abort update."));
810 d->autoUpdaterAbortButton->setIcon(QIcon(
":/icons/x.png"));
811 this->connect(d->autoUpdaterAbortButton, SIGNAL(clicked()),
812 SLOT(abortAutoUpdater()));
813 d->autoUpdaterStatusBarWidget->layout()->addWidget(d->autoUpdaterAbortButton);
816 void MainWindow::initIP2CUpdater()
818 static const int PROGRESSBAR_WIDTH = 220;
820 d->ip2cUpdateProgressBar = mkStdProgressBarForStatusBar();
821 d->ip2cUpdateProgressBar->setFormat(tr(
"IP2C Update"));
822 d->ip2cUpdateProgressBar->hide();
823 d->ip2cUpdateProgressBar->setMaximumWidth(PROGRESSBAR_WIDTH);
824 d->ip2cUpdateProgressBar->setMinimumWidth(PROGRESSBAR_WIDTH);
825 statusBar()->addPermanentWidget(d->ip2cUpdateProgressBar);
828 void MainWindow::initIRCDock()
830 d->ircDock =
new IRCDock(
this);
831 d->menuView->addAction(d->ircDock->toggleViewAction());
832 d->ircDock->toggleViewAction()->setText(tr(
"&IRC"));
833 d->ircDock->toggleViewAction()->setShortcut(tr(
"CTRL+I"));
835 this->addDockWidget(Qt::BottomDockWidgetArea, d->ircDock);
839 this->d->ircDock->setVisible(
true);
840 this->d->ircDock->performNetworkAutojoins();
844 void MainWindow::initLogDock()
846 d->logDock =
new LogDock(
this);
847 d->menuView->addAction(d->logDock->toggleViewAction());
848 d->logDock->toggleViewAction()->setText(tr(
"&Log"));
849 d->logDock->toggleViewAction()->setShortcut(tr(
"CTRL+L"));
851 this->addDockWidget(Qt::BottomDockWidgetArea, d->logDock);
853 connect(&gLog, SIGNAL(newEntry(
const QString&)), d->logDock, SLOT(appendLogEntry(
const QString&)));
856 d->logDock->appendLogEntry(gLog.content());
859 void MainWindow::initMainDock()
861 setDockNestingEnabled(
true);
865 d->mainDock =
new QDockWidget(tr(
"Servers"));
866 d->mainDock->setTitleBarWidget(
new QWidget(
this));
867 d->mainDock->setObjectName(
"ServerList");
868 d->mainDock->setFeatures(QDockWidget::NoDockWidgetFeatures);
869 d->mainDock->setWidget(centralWidget());
870 setCentralWidget(
nullptr);
871 addDockWidget(Qt::RightDockWidgetArea, d->mainDock);
874 void MainWindow::initServerDetailsDock()
877 d->menuView->addAction(d->detailsDock->toggleViewAction());
878 d->detailsDock->toggleViewAction()->setText(tr(
"Server &details"));
879 d->detailsDock->toggleViewAction()->setShortcut(tr(
"CTRL+D"));
880 d->detailsDock->hide();
881 addDockWidget(Qt::BottomDockWidgetArea, d->detailsDock);
883 d->detailsDock->connect(d->serverList, SIGNAL(serversSelected(QList<ServerPtr>&)), SLOT(displaySelection(QList<ServerPtr> &)));
886 void MainWindow::initServerFilterDock()
890 d->menuView->addAction(d->serverFilterDock->toggleViewAction());
891 d->serverFilterDock->hide();
892 this->addDockWidget(Qt::RightDockWidgetArea, d->serverFilterDock);
897 bool isEnabled = gConfig.doomseeker.bUseTrayIcon;
898 if (!isEnabled || !QSystemTrayIcon::isSystemTrayAvailable())
900 if (d->trayIcon !=
nullptr)
903 d->trayIcon =
nullptr;
906 if (d->trayIconMenu !=
nullptr)
908 delete d->trayIconMenu;
909 d->trayIconMenu =
nullptr;
912 else if (d->trayIcon ==
nullptr)
915 d->trayIconMenu =
new QMenu(
this);
916 trayAction = d->trayIconMenu->addAction(
"Exit");
917 connect(trayAction, SIGNAL(triggered()),
this, SLOT(quitProgram()));
920 d->trayIcon =
new QSystemTrayIcon(
this);
921 connect(d->trayIcon, SIGNAL(activated(QSystemTrayIcon::ActivationReason)),
this, SLOT(trayIcon_activated(QSystemTrayIcon::ActivationReason)));
923 updateTrayIconTooltipAndLogTotalRefresh();
925 d->trayIcon->setContextMenu(d->trayIconMenu);
926 d->trayIcon->setIcon(QIcon(
":/icon.png"));
927 d->trayIcon->setVisible(
true);
931 void MainWindow::ip2cDownloadProgress(qint64 current, qint64 max)
933 d->ip2cUpdateProgressBar->setMaximum(max);
934 d->ip2cUpdateProgressBar->setValue(current);
937 void MainWindow::ip2cJobsFinished()
939 d->menuActionUpdateIP2C->setEnabled(
true);
940 d->serverList->updateCountryFlags();
941 d->ip2cUpdateProgressBar->hide();
943 if (d->ip2cLoader !=
nullptr)
945 delete d->ip2cLoader;
946 d->ip2cLoader =
nullptr;
950 void MainWindow::ip2cStartUpdate()
952 if (d->ip2cLoader !=
nullptr)
958 d->menuActionUpdateIP2C->setEnabled(
false);
959 d->ip2cUpdateProgressBar->show();
963 d->ip2cLoader->update();
968 return hasCustomServers() || isAnyMasterEnabled();
971 bool MainWindow::isAnyMasterEnabled()
const
973 for (
int i = 0; i < d->masterManager->numMasters(); ++i)
984 bool MainWindow::isEffectivelyActiveWindow()
const
986 return this->isActiveWindow() || DoomseekerConfigurationDialog::isOpen();
989 void MainWindow::masterManagerMessages(
MasterClient *pSender,
const QString &title,
const QString &content,
bool isError)
992 QString message = tr(
"Master server for %1: %2").arg(pSender->
plugin()->data()->name).arg(content);
996 message = tr(
"Error: %1").arg(message);
997 statusBar()->showMessage(message);
1003 void MainWindow::masterManagerMessagesImportant(
MasterClient *pSender,
const Message &objMessage)
1005 QString strFullMessage = tr(
"%1: %2")
1006 .arg(pSender->
plugin()->data()->name)
1008 d->importantMessagesWidget->addMessage(strFullMessage, objMessage.
timestamp());
1011 void MainWindow::menuBuddies()
1013 d->buddiesList->setVisible(!d->buddiesList->isVisible());
1016 void MainWindow::menuCreateServer()
1020 dialog->setWindowIcon(this->windowIcon());
1024 void MainWindow::menuHelpAbout()
1027 d->autoRefreshTimer.stop();
1032 void MainWindow::menuHelpHelp()
1036 QMessageBox::critical(
this, tr(
"Help error"), tr(
"No help found."), QMessageBox::Ok, QMessageBox::Ok);
1040 bool bSuccess = QDesktopServices::openUrl(HELP_SITE_URL);
1044 QMessageBox::critical(
this, tr(
"Help error"), tr(
"Failed to open URL:\n%1").arg(HELP_SITE_URL), QMessageBox::Ok, QMessageBox::Ok);
1049 void MainWindow::menuIRCOptions()
1052 dialog.initOptionsList();
1055 if (d->ircDock !=
nullptr)
1057 d->ircDock->applyAppearanceSettings();
1062 d->ircDock->sounds().loadFromConfig();
1066 void MainWindow::menuLog()
1068 d->logDock->setVisible(!d->logDock->isVisible());
1071 void MainWindow::menuManageDemos()
1074 dm.setWindowIcon(this->windowIcon());
1078 void MainWindow::menuOptionsConfigure()
1080 DoomseekerConfigurationDialog::openConfiguration(
this);
1083 void MainWindow::menuRecordDemo()
1085 gConfig.doomseeker.bRecordDemo = d->menuActionRecordDemo->isChecked();
1088 void MainWindow::menuUpdateIP2C()
1092 connect(&updateBox, SIGNAL(accepted()),
this, SLOT(ip2cStartUpdate()));
1097 void MainWindow::menuViewIRC()
1099 d->ircDock->setVisible(!d->ircDock->isVisible());
1102 void MainWindow::menuWadSeeker()
1104 if (gWadseekerShow->checkWadseekerValidity(
this))
1107 wadseeker->setAttribute(Qt::WA_DeleteOnClose);
1112 QProgressBar *MainWindow::mkStdProgressBarForStatusBar()
1114 auto pBar =
new QProgressBar(statusBar());
1115 pBar->setAlignment(Qt::AlignCenter);
1116 pBar->setTextVisible(
true);
1117 pBar->setSizePolicy(QSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed));
1121 void MainWindow::notifyFirstRun()
1124 QMessageBox::information(
nullptr, tr(
"Welcome to Doomseeker"),
1125 tr(
"Before you start browsing for servers, please ensure that Doomseeker is properly configured."));
1126 d->menuActionConfigure->trigger();
1129 void MainWindow::onAutoUpdaterDownloadAndInstallConfirmationRequest()
1131 d->updatesConfirmationWidget->show();
1134 void MainWindow::onAutoUpdaterFileProgress(qint64 bytesReceived, qint64 bytesTotal)
1136 d->autoUpdaterFileProgressBar->setValue(bytesReceived);
1137 d->autoUpdaterFileProgressBar->setMaximum(bytesTotal);
1140 void MainWindow::onAutoUpdaterFinish()
1142 showAndLogStatusMessage(tr(
"Program update detection & download finished with status: [%1] %2")
1143 .arg((
int)d->autoUpdater->errorCode()).arg(d->autoUpdater->errorString()));
1144 gConfig.autoUpdates.bPerformUpdateOnNextRun =
false;
1145 if (d->autoUpdater->errorCode() == AutoUpdater::EC_Ok)
1148 if (channel == *d->updateChannelOnUpdateStart)
1150 if (!d->autoUpdater->downloadedPackagesFilenames().isEmpty())
1152 gLog << tr(
"Updates will be installed on next program start.");
1153 d->updatesDownloadedWidget->show();
1154 gConfig.autoUpdates.bPerformUpdateOnNextRun =
true;
1158 gLog << tr(
"Update channel was changed during update process. Discarding update.");
1160 gConfig.saveToFile();
1161 d->autoUpdaterStatusBarWidget->hide();
1162 d->updatesConfirmationWidget->hide();
1163 d->autoUpdater->deleteLater();
1164 d->autoUpdater =
nullptr;
1167 void MainWindow::onAutoUpdaterOverallProgress(
int current,
int total,
1170 d->autoUpdaterOverallProgressBar->setValue(current);
1171 d->autoUpdaterOverallProgressBar->setMaximum(total);
1172 d->autoUpdaterOverallProgressBar->setFormat(msg);
1175 void MainWindow::postInitAppStartup()
1178 d->serverFilterDock->setFilterInfo(gConfig.serverFilter.info);
1184 if (gPlugins->numPlugins() > 0)
1186 bool bGettingServers =
false;
1187 bool queryOnStartup = gConfig.doomseeker.bQueryOnStartup;
1194 if (isAnyMasterEnabled())
1196 bGettingServers =
true;
1200 gLog << tr(
"Query on startup warning: No master servers are enabled in the Query menu.");
1205 if (!bGettingServers && hasCustomServers())
1210 refreshCustomServers();
1217 QString error = tr(
"Doomseeker was unable to find any plugin libraries.\n"
1218 "Although the application will still work it will not be possible "
1219 "to fetch any server info or launch any game.\n\n"
1220 "Please check if there are any files in \"engines/\" directory.\n"
1221 "To fix this problem you may try downloading Doomseeker "
1222 "again from the site specified in the Help|About box and reinstalling "
1224 QMessageBox::critical(
nullptr, tr(
"Doomseeker critical error"), error);
1228 QueryMenuAction *MainWindow::queryMenuActionForPlugin(
const EnginePlugin *plugin)
1230 if (plugin ==
nullptr)
1233 if (d->queryMenuPorts.contains(plugin))
1234 return d->queryMenuPorts[plugin];
1239 void MainWindow::quitProgram()
1241 d->bWantToQuit =
true;
1242 QApplication::closeAllWindows();
1245 void MainWindow::refreshCustomServers()
1247 for (
const ServerPtr &server : d->serverList->servers())
1249 if (server->isCustom())
1250 gRefresher->registerServer(server.data());
1254 void MainWindow::refreshServersOnList()
1256 for (
const ServerPtr &server : d->serverList->servers())
1258 gRefresher->registerServer(server.data());
1262 void MainWindow::refreshThreadBeginsWork()
1264 statusBar()->showMessage(tr(
"Querying..."));
1265 d->taskbarProgress->show();
1268 void MainWindow::refreshThreadEndsWork()
1270 d->toolBarGetServers->setEnabled(
true);
1272 d->serverList->cleanUpRightNow();
1273 statusBar()->showMessage(tr(
"Done"));
1274 updateTrayIconTooltipAndLogTotalRefresh();
1275 d->taskbarProgress->hide();
1277 if (d->bTotalRefreshInProcess)
1280 d->bTotalRefreshInProcess =
false;
1281 QList<ServerPtr> selectedServers = d->serverList->selectedServers();
1282 d->detailsDock->displaySelection(selectedServers);
1285 void MainWindow::restartAndInstallUpdatesNow()
1291 void MainWindow::runGame(
const ServerPtr &server)
1293 if (d->connectionHandler)
1294 delete d->connectionHandler;
1297 d->connectionHandler->run();
1302 assert(plugin !=
nullptr);
1304 QueryMenuAction *pAction = queryMenuActionForPlugin(plugin);
1305 if (pAction !=
nullptr)
1307 pAction->setChecked(bEnabled);
1308 if (plugin->data()->hasMasterClient())
1309 plugin->data()->masterClient->
setEnabled(bEnabled);
1310 if (plugin->data()->hasBroadcast())
1311 plugin->data()->broadcast->setEnabled(bEnabled);
1312 d->serversStatusesWidgets[plugin]->setMasterEnabledStatus(bEnabled);
1316 void MainWindow::serverAddedToList(
const ServerPtr &pServer)
1318 if (pServer->isKnown())
1320 const QString &gameMode = pServer->gameMode().
name();
1321 d->serverFilterDock->addGameModeToComboBox(gameMode);
1327 assert(this->d->updaterInstallerErrorCode == 0 &&
1328 "MainWindow::setDisplayUpdaterProcessFailure()");
1329 this->d->updaterInstallerErrorCode = errorCode;
1330 QTimer::singleShot(0,
this, SLOT(showUpdaterProcessErrorDialog()));
1335 assert(this->d->updaterInstallerErrorCode == 0 &&
1336 "MainWindow::setDisplayUpdateInstallerError()");
1337 this->d->updaterInstallerErrorCode = errorCode;
1338 QTimer::singleShot(0,
this, SLOT(showUpdateInstallErrorDialog()));
1341 void MainWindow::setupIcons()
1343 QStyle &style = *QApplication::style();
1346 d->menuActionQuit->setIcon(style.standardIcon(QStyle::SP_TitleBarCloseButton));
1349 d->menuActionHelp->setIcon(style.standardIcon(QStyle::SP_MessageBoxQuestion));
1350 d->menuActionAbout->setIcon(style.standardIcon(QStyle::SP_MessageBoxInformation));
1353 void MainWindow::setupToolBar()
1355 QToolBar *pToolBar =
new QToolBar(tr(
"Main Toolbar"),
this);
1356 pToolBar->setMovable(
false);
1357 pToolBar->setObjectName(
"Toolbar");
1360 d->toolBarGetServers =
new QAction(QIcon(
":/icons/refresh.png"), tr(
"Get Servers"), pToolBar);
1364 pToolBar->addAction(d->toolBarGetServers);
1367 pToolBar->addSeparator();
1368 pToolBar->addAction(d->menuActionCreateServer);
1369 pToolBar->addAction(d->menuActionWadseeker);
1372 pToolBar->addSeparator();
1373 pToolBar->addAction(d->menuActionManageDemos);
1374 pToolBar->addAction(d->menuActionRecordDemo);
1376 pToolBar->addSeparator();
1379 pToolBar->addAction(d->buddiesList->toggleViewAction());
1380 pToolBar->addAction(d->logDock->toggleViewAction());
1381 pToolBar->addAction(d->ircDock->toggleViewAction());
1382 pToolBar->addAction(d->serverFilterDock->toggleViewAction());
1383 pToolBar->addAction(d->detailsDock->toggleViewAction());
1386 QLineEdit *qs = d->serverFilterDock->createQuickSearch();
1387 qs->setSizePolicy(QSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed));
1388 qs->setMinimumWidth(175);
1389 qs->setMaximumWidth(175);
1391 QWidget *searchSeparator =
new QWidget();
1392 searchSeparator->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::MinimumExpanding);
1393 pToolBar->addWidget(searchSeparator);
1394 pToolBar->addWidget(
new QLabel(tr(
"Search:"), pToolBar));
1395 pToolBar->addWidget(qs);
1397 this->addToolBar(Qt::TopToolBarArea, pToolBar);
1398 setUnifiedTitleAndToolBarOnMac(
true);
1399 connect(pToolBar, SIGNAL(actionTriggered(QAction*)),
this, SLOT(toolBarAction(QAction*)));
1402 void MainWindow::showAndLogStatusMessage(
const QString &message)
1405 statusBar()->showMessage(message);
1408 void MainWindow::showEvent(QShowEvent *event)
1412 d->taskbarButton->setWindow(windowHandle());
1415 void MainWindow::showInstallFreedoomDialog()
1417 if (!d->freedoomDialog.isNull())
1419 d->freedoomDialog->activateWindow();
1423 dialog->setAttribute(Qt::WA_DeleteOnClose);
1425 d->freedoomDialog = dialog;
1428 void MainWindow::showProgramArgsHelp()
1431 dialog->setAttribute(Qt::WA_DeleteOnClose);
1435 void MainWindow::showServerJoinCommandLine(
const ServerPtr &server)
1439 GameDemo demo = gConfig.doomseeker.bRecordDemo ? GameDemo::Unmanaged : GameDemo::NoDemo;
1441 this->connect(builder, SIGNAL(commandLineBuildFinished()), SLOT(showServerJoinCommandLineOnBuilderFinished()));
1445 void MainWindow::showServerJoinCommandLineOnBuilderFinished()
1451 QString execPath = cli.
executable.absoluteFilePath();
1452 QStringList args = cli.
args;
1457 CopyTextDlg ctd(execPath +
" " + args.join(
" "), builder->server()->name(),
this);
1462 if (!builder->error().isEmpty())
1464 QMessageBox::critical(
this, tr(
"Doomseeker - show join command line"),
1465 tr(
"The command line cannot be built:\n%1").arg(builder->error()));
1468 builder->deleteLater();
1471 void MainWindow::showUpdaterProcessErrorDialog()
1473 QString explanation;
1474 if (this->d->updaterInstallerErrorCode != UpdateInstaller::PEC_GeneralFailure)
1476 QString errorCodeExplanation = UpdateInstaller::processErrorCodeToStr(
1478 explanation = tr(
"Update installation problem:\n%1").arg(errorCodeExplanation);
1481 explanation = tr(
"Update installation failed.");
1482 QMessageBox::critical(
this, tr(
"Doomseeker - Auto Update problem"),
1483 tr(
"%1\n\nRemaining updates have been discarded.").arg(explanation));
1486 void MainWindow::showUpdateInstallErrorDialog()
1488 QString error = UpdateInstaller::errorCodeToStr(
1490 QString msg = tr(
"Update install problem:\n%1\n\nRemaining updates have been discarded.").arg(error);
1491 QMessageBox::critical(
this, tr(
"Doomseeker - Auto Update problem"), msg);
1494 void MainWindow::stopAutoRefreshTimer()
1496 d->autoRefreshTimer.stop();
1501 QueryMenuAction *pAction = queryMenuActionForPlugin(plugin);
1502 assert(pAction !=
nullptr);
1507 void MainWindow::toolBarAction(QAction *pAction)
1509 if (pAction == d->toolBarGetServers)
1513 void MainWindow::trayIcon_activated(QSystemTrayIcon::ActivationReason reason)
1515 if (reason == QSystemTrayIcon::Trigger)
1517 if (isMinimized() || !isVisible())
1519 d->bWasMaximized ==
true ? showMaximized() : showNormal();
1522 else if (gConfig.doomseeker.bCloseToTrayIcon)
1529 void MainWindow::updateDynamicAppearance()
1531 d->tableServers->setShowGrid(gConfig.doomseeker.bDrawGridInServerTable);
1532 d->serverList->redraw();
1533 d->serverList->cleanUpForce();
1538 void MainWindow::updateMasterAddresses()
1540 for (
int i = 0; i < d->masterManager->numMasters(); i++)
1541 (*d->masterManager)[i]->updateAddress();
1546 d->serverList->applyFilter(filterInfo);
1555 count += status->count();
1560 void MainWindow::updateRefreshProgress()
1563 d->taskbarProgress->setMaximum(count.numServers);
1564 d->taskbarProgress->setValue(count.numServers - count.numRefreshing);
1565 updateTrayIconTooltip(count);
1570 if (d->trayIcon !=
nullptr)
1573 tip += tr(
"Generic servers: %1\n").arg(count.numGenericServers);
1574 tip += tr(
"Custom servers: %1\n").arg(count.numCustomServers);
1575 tip += tr(
"LAN servers: %1\n").arg(count.numLanServers);
1576 tip += tr(
"Human players: %1").arg(count.numHumanPlayers);
1577 d->trayIcon->setToolTip(tip);
1581 void MainWindow::updateTrayIconTooltipAndLogTotalRefresh()
1584 updateTrayIconTooltip(count);
1586 if (d->bTotalRefreshInProcess)
1588 gLog << tr(
"Finished refreshing. Servers on the list: %1 "
1589 "(+%2 custom, +%3 LAN). Players: %4.")
1590 .arg(count.numGenericServers).arg(count.numCustomServers)
1591 .arg(count.numLanServers).arg(count.numHumanPlayers);