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 <QDesktopWidget>
89 #include <QDockWidget>
91 #include <QHeaderView>
94 #include <QMessageBox>
95 #include <QMessageBox>
97 #include <QProgressBar>
98 #include <QSizePolicy>
101 const QString MainWindow::HELP_SITE_URL =
"https://doomseeker.drdteam.org/help";
110 class QueryMenuAction :
public QAction
116 this->pPlugin = plugin;
118 if (plugin !=
nullptr)
120 connect(
this, SIGNAL(toggled(
bool)), plugin->data()->masterClient,
121 SLOT(setEnabled(
bool)));
122 connect(
this, SIGNAL(toggled(
bool)), statusWidget, SLOT(setMasterEnabledStatus(
bool)));
135 DClass<MainWindow> :
public Ui::MainWindowWnd
138 PrivData() : bTotalRefreshInProcess(false), buddiesList(nullptr),
139 bWasMaximized(false), bWantToQuit(false), logDock(nullptr),
140 masterManager(nullptr), trayIcon(nullptr), trayIconMenu(nullptr)
144 QApplication *application;
145 QTimer autoRefreshTimer;
148 QWidget *autoUpdaterStatusBarWidget;
149 QPushButton *autoUpdaterAbortButton;
150 QLabel *autoUpdaterLabel;
151 QProgressBar *autoUpdaterFileProgressBar;
152 QProgressBar *autoUpdaterOverallProgressBar;
158 bool bTotalRefreshInProcess;
176 QProgressBar *ip2cUpdateProgressBar;
180 QPointer<FreedoomDialog> freedoomDialog;
185 QHash<const EnginePlugin *, QueryMenuAction *> queryMenuPorts;
186 QHash<const EnginePlugin *, ServersStatusWidget *> serversStatusesWidgets;
187 QAction *toolBarGetServers;
188 QSystemTrayIcon *trayIcon;
192 int updaterInstallerErrorCode;
195 QDockWidget *mainDock;
205 d->autoUpdater =
nullptr;
206 d->mainDock =
nullptr;
207 d->connectionHandler =
nullptr;
209 d->updaterInstallerErrorCode = 0;
211 d->application = application;
213 this->setAttribute(Qt::WA_DeleteOnClose,
true);
217 d->taskbarProgress = d->taskbarButton->progress();
221 initAutoUpdaterWidgets();
223 d->updatesConfirmationWidget->hide();
224 d->updatesDownloadedWidget->hide();
227 #ifndef WITH_AUTOUPDATES
228 d->menuActionCheckForUpdates->setVisible(
false);
235 d->menuView->addAction(d->buddiesList->toggleViewAction());
236 d->buddiesList->toggleViewAction()->setText(MainWindow::tr(
"&Buddies"));
237 d->buddiesList->toggleViewAction()->setShortcut(MainWindow::tr(
"CTRL+B"));
239 connect(d->buddiesList, SIGNAL(joinServer(ServerPtr)),
this, SLOT(runGame(ServerPtr)));
240 d->buddiesList->hide();
241 this->addDockWidget(Qt::LeftDockWidgetArea, d->buddiesList);
244 initServerFilterDock();
246 splitDockWidget(d->mainDock, d->serverFilterDock, Qt::Horizontal);
249 d->serverList =
new ServerList(d->tableServers,
this);
253 d->serverList->connect(d->broadcastManager,
254 SIGNAL(newServerDetected(ServerPtr,
int)), SLOT(registerServer(ServerPtr)));
255 d->serverList->connect(d->broadcastManager,
256 SIGNAL(serverLost(ServerPtr)), SLOT(removeServer(ServerPtr)));
258 initServerDetailsDock();
259 tabifyDockWidget(d->ircDock, d->detailsDock);
262 d->menuActionRecordDemo->setChecked(gConfig.doomseeker.bRecordDemo);
266 d->masterManager->setBroadcastManager(d->broadcastManager);
267 d->buddiesList->scan(d->masterManager);
268 connect(d->masterManager, SIGNAL(masterMessage(
MasterClient*,
const QString&,
const QString&,
bool)),
269 this, SLOT(masterManagerMessages(
MasterClient*,
const QString&,
const QString&,
bool)));
274 fillQueryMenu(d->masterManager);
277 QList<ServerPtr> customServers = d->masterManager->customServs()->readConfig();
278 for (ServerPtr server : customServers)
280 d->serverList->registerServer(server);
283 setWindowIcon(Application::icon());
286 initAutoRefreshTimer();
287 connect(&d->autoRefreshTimer, SIGNAL(timeout()),
this, SLOT(autoRefreshTimer_timeout()));
298 d->menuActionUpdateIP2C->setEnabled(
false);
301 d->ip2cLoader->load();
303 restoreState(QByteArray::fromBase64(gConfig.doomseeker.mainWindowState.toUtf8()));
304 restoreGeometry(gConfig.doomseeker.mainWindowGeometry);
308 QTimer::singleShot(1,
this, SLOT(postInitAppStartup()));
311 MainWindow::~MainWindow()
314 gConfig.doomseeker.mainWindowGeometry = saveGeometry();
315 gConfig.doomseeker.mainWindowState = saveState().toBase64();
317 if (d->updateChannelOnUpdateStart !=
nullptr)
318 delete d->updateChannelOnUpdateStart;
319 if (d->autoUpdater !=
nullptr)
321 d->autoUpdater->disconnect();
322 delete d->autoUpdater;
324 if (d->connectionHandler)
325 delete d->connectionHandler;
327 QList<QAction *> menuQueryActions = d->menuQuery->actions();
328 QList<QAction *>::iterator it;
329 for (QAction *action : menuQueryActions)
331 QString pluginName = action->text();
333 if (!pluginName.isEmpty())
335 IniSection pluginConfig = gConfig.iniSectionForPlugin(pluginName);
336 pluginConfig[
"Query"] = action->isChecked();
340 if (d->trayIcon !=
nullptr)
342 d->trayIcon->setVisible(
false);
344 d->trayIcon =
nullptr;
347 if (d->trayIconMenu !=
nullptr)
349 delete d->trayIconMenu;
350 d->trayIconMenu =
nullptr;
353 delete d->serverList;
355 if (d->masterManager !=
nullptr)
356 delete d->masterManager;
358 if (d->ip2cLoader !=
nullptr)
359 delete d->ip2cLoader;
362 void MainWindow::abortAutoUpdater()
364 if (d->autoUpdater !=
nullptr)
365 d->autoUpdater->abort();
368 void MainWindow::autoRefreshTimer_timeout()
370 if (gConfig.doomseeker.bQueryAutoRefreshDontIfActive && !isMinimized())
372 if (QApplication::activeWindow() !=
nullptr)
379 void MainWindow::blockRefreshButtons()
381 d->toolBarGetServers->setEnabled(
false);
386 return d->buddiesList;
389 void MainWindow::changeEvent(QEvent *event)
391 if (event->type() == QEvent::ActivationChange && isActiveWindow() && !isMinimized() && !isHidden())
393 d->serverList->cleanUp();
396 QMainWindow::changeEvent(event);
399 void MainWindow::checkForUpdates(
bool bUserTriggered)
401 if (d->autoUpdater !=
nullptr)
403 if (d->autoUpdater->isRunning())
405 QMessageBox::warning(
this, tr(
"Doomseeker - Auto Update"),
406 tr(
"Update is already in progress."));
411 delete d->autoUpdater;
412 d->autoUpdater =
nullptr;
415 gLog << tr(
"Removing old update packages from local temporary space.");
416 QStringList removeFilter(QString(
"%1*").arg(DataPaths::UPDATE_PACKAGE_FILENAME_PREFIX));
420 showAndLogStatusMessage(tr(
"Checking for updates..."));
422 this->connect(d->autoUpdater, SIGNAL(statusMessage(QString)),
423 SLOT(showAndLogStatusMessage(QString)));
424 this->connect(d->autoUpdater, SIGNAL(finished()),
425 SLOT(onAutoUpdaterFinish()));
426 this->connect(d->autoUpdater, SIGNAL(downloadAndInstallConfirmationRequested()),
427 SLOT(onAutoUpdaterDownloadAndInstallConfirmationRequest()));
428 this->connect(d->autoUpdater, SIGNAL(overallProgress(
int,
int,
const QString&)),
429 SLOT(onAutoUpdaterOverallProgress(
int,
int,
const QString&)));
430 this->connect(d->autoUpdater, SIGNAL(packageDownloadProgress(qint64,qint64)),
431 SLOT(onAutoUpdaterFileProgress(qint64,qint64)));
433 QMap<QString, QList<QString> > ignoredPackagesRevisions;
436 for (
const QString &package : gConfig.autoUpdates.lastKnownUpdateRevisions.keys())
438 QString revision = gConfig.autoUpdates.lastKnownUpdateRevisions[package];
441 ignoredPackagesRevisions.insert(package, list);
444 d->autoUpdater->setIgnoreRevisions(ignoredPackagesRevisions);
447 d->autoUpdater->setChannel(channel);
448 *d->updateChannelOnUpdateStart = channel;
450 bool bRequireConfirmation =
true;
453 bRequireConfirmation = (gConfig.autoUpdates.updateMode
456 d->autoUpdater->setRequireDownloadAndInstallConfirmation(bRequireConfirmation);
457 d->autoUpdaterStatusBarWidget->show();
458 d->autoUpdater->start();
464 const bool bUserTriggered =
true;
465 checkForUpdates(!bUserTriggered);
470 const bool bUserTriggered =
true;
471 checkForUpdates(bUserTriggered);
474 void MainWindow::closeEvent(QCloseEvent *event)
479 if (d->trayIcon !=
nullptr && gConfig.doomseeker.bCloseToTrayIcon && !d->bWantToQuit)
481 d->bWasMaximized = isMaximized();
489 void MainWindow::confirmUpdateInstallation()
491 assert(d->autoUpdater !=
nullptr &&
"MainWindow::confirmUpdateInstallation()");
492 d->updatesConfirmationWidget->hide();
493 d->autoUpdater->confirmDownloadAndInstall();
496 void MainWindow::connectIP2CLoader()
498 this->connect(d->ip2cLoader, SIGNAL(finished()), SLOT(ip2cJobsFinished()));
499 this->connect(d->ip2cLoader, SIGNAL(downloadProgress(qint64,qint64)),
500 SLOT(ip2cDownloadProgress(qint64,qint64)));
503 void MainWindow::discardUpdates()
505 assert(d->autoUpdater !=
nullptr &&
"MainWindow::confirmUpdateInstallation()");
506 d->updatesConfirmationWidget->hide();
509 const QList<UpdatePackage> &pkgList = d->autoUpdater->newUpdatePackages();
512 gConfig.autoUpdates.lastKnownUpdateRevisions.insert(pkg.name, pkg.revision);
514 d->autoUpdater->abort();
520 connect(gRefresher, SIGNAL(block()),
this, SLOT(blockRefreshButtons()));
521 connect(gRefresher, SIGNAL(finishedQueryingMaster(
MasterClient*)),
this, SLOT(finishedQueryingMaster(
MasterClient*)));
522 connect(gRefresher, SIGNAL(sleepingModeEnter()),
this, SLOT(refreshThreadEndsWork()));
523 connect(gRefresher, SIGNAL(sleepingModeEnter()), d->buddiesList, SLOT(scan()));
524 connect(gRefresher, SIGNAL(sleepingModeExit()),
this, SLOT(refreshThreadBeginsWork()));
527 connect(d->menuActionAbout, SIGNAL(triggered()),
this, SLOT(menuHelpAbout()));
528 connect(d->menuActionAboutQt, SIGNAL(triggered()), d->application, SLOT(aboutQt()));
529 connect(d->menuActionBuddies, SIGNAL(triggered()),
this, SLOT(menuBuddies()));
530 connect(d->menuActionConfigure, SIGNAL(triggered()),
this, SLOT(menuOptionsConfigure()));
531 connect(d->menuActionCreateServer, SIGNAL(triggered()),
this, SLOT(menuCreateServer()));
532 connect(d->menuActionHelp, SIGNAL(triggered()),
this, SLOT (menuHelpHelp()));
533 connect(d->menuActionIRCOptions, SIGNAL(triggered()),
this, SLOT(menuIRCOptions()));
534 connect(d->menuActionLog, SIGNAL(triggered()),
this, SLOT(menuLog()));
535 connect(d->menuActionManageDemos, SIGNAL(triggered()),
this, SLOT(menuManageDemos()));
536 connect(d->menuActionRecordDemo, SIGNAL(triggered()),
this, SLOT(menuRecordDemo()));
537 connect(d->menuActionUpdateIP2C, SIGNAL(triggered()),
this, SLOT(menuUpdateIP2C()));
538 connect(d->menuActionQuit, SIGNAL(triggered()),
this, SLOT(quitProgram()));
539 connect(d->menuActionViewIRC, SIGNAL(triggered()),
this, SLOT(menuViewIRC()));
540 connect(d->menuActionWadseeker, SIGNAL(triggered()),
this, SLOT(menuWadSeeker()));
543 connect(d->serverFilterDock, SIGNAL(nonEmptyServerGroupingAtTopToggled(
bool)),
544 d->serverList, SLOT(setGroupServersWithPlayersAtTop(
bool)));
547 connect(d->serverList, SIGNAL(serverDoubleClicked(ServerPtr)),
this, SLOT(runGame(ServerPtr)));
548 connect(d->serverList, SIGNAL(displayServerJoinCommandLine(
const ServerPtr&)),
this, SLOT(showServerJoinCommandLine(
const ServerPtr&)));
549 connect(d->serverList, SIGNAL(findMissingWADs(
const ServerPtr&)),
this, SLOT(findMissingWADs(
const ServerPtr&)));
550 connect(d->serverList, SIGNAL(serverInfoUpdated(ServerPtr)),
this, SLOT(serverAddedToList(ServerPtr)));
551 connect(d->buddiesList, SIGNAL(scanCompleted()), d->serverList, SLOT(redraw()));
558 for (
unsigned i = 0; i < gPlugins->numPlugins(); ++i)
561 if (!plugin->data()->hasMasterClient() && !plugin->data()->hasBroadcast())
564 if (plugin->data()->hasMasterClient())
566 MasterClient *pMasterClient = plugin->data()->masterClient;
567 pMasterClient->updateAddress();
568 masterManager->addMaster(pMasterClient);
571 if (plugin->data()->hasBroadcast())
572 d->broadcastManager->registerPlugin(plugin);
576 d->serversStatusesWidgets.insert(plugin, statusWidget);
578 this->connect(statusWidget, SIGNAL(clicked(
const EnginePlugin*)),
580 this->connect(statusWidget, SIGNAL(counterUpdated()),
581 SLOT(updateRefreshProgress()));
583 statusBar()->addPermanentWidget(statusWidget);
585 QString name = gPlugins->info(i)->data()->name;
586 auto query =
new QueryMenuAction(plugin, statusWidget, d->menuQuery);
587 d->queryMenuPorts.insert(plugin, query);
589 d->menuQuery->addAction(query);
591 query->setCheckable(
true);
592 query->setIcon(plugin->icon());
593 query->setText(name);
595 IniSection pluginConfig = gConfig.iniSectionForPlugin(name);
599 bool enabled = pluginConfig[
"Query"];
611 void MainWindow::findMissingWADs(
const ServerPtr &server)
614 QList<PWad> wads = server->wads();
615 PathFinder pathFinder = server->wadPathFinder();
616 QList<PWad> missingWads;
617 QList<PWad> incompatibleWads;
620 checkWadsDlg->addWads(wads);
621 const CheckResult checkResults = checkWadsDlg->checkWads();
623 for (
const PWad &wad : checkResults.missingWads)
627 incompatibleWads << checkResults.incompatibleWads;
629 if (missingWads.isEmpty() && incompatibleWads.isEmpty())
631 QMessageBox::information(
this, tr(
"All WADs found"), tr(
"All of the WADs used by this server are present."));
636 dialog.setAllowIgnore(
false);
637 if (dialog.exec() == QDialog::Accepted && dialog.decision() == MissingWadsDialog::Install)
639 if (!gWadseekerShow->checkWadseekerValidity(
this))
642 wadseeker->setCustomSites(server->allWebSites());
643 wadseeker->
setWads(dialog.filesToDownload());
644 wadseeker->setAttribute(Qt::WA_DeleteOnClose);
652 updateMasterAddresses();
653 gRefresher->setDelayBetweenResends(gConfig.doomseeker.querySpeed().delayBetweenSingleServerAttempts);
656 if (configDialog.wasAppearanceChanged())
658 updateDynamicAppearance();
663 if (configDialog.isRestartNeeded())
665 QString warningRestartNeeded = tr(
"Doomseeker needs to be restarted for some changes to be applied.");
666 d->importantMessagesWidget->addMessage(warningRestartNeeded);
670 if (lookupHostsChanged)
671 d->serverList->lookupHosts();
674 if (configDialog.customServersChanged())
676 d->serverList->removeCustomServers();
677 QList<ServerPtr> servers = d->masterManager->customServs()->readConfig();
678 for (ServerPtr server : servers)
680 d->serverList->registerServer(server);
682 refreshCustomServers();
686 void MainWindow::finishedQueryingMaster(
MasterClient *master)
688 if (master ==
nullptr)
691 for (
int i = 0; i < master->numServers(); i++)
692 d->serverList->registerServer((*master)[i]);
695 void MainWindow::fixIconsDpi()
698 QIcon icon(
":/icons/exclamation_16.png");
699 d->lblExclamation1->setPixmap(icon.pixmap(16));
700 d->lblExclamation2->setPixmap(icon.pixmap(16));
703 void MainWindow::getServers()
708 QString message = tr(
"Doomseeker is unable to proceed with the refresh"
709 " operation because the following problem has occurred:\n\n");
711 if (gPlugins->numPlugins() == 0)
712 message += tr(
"Plugins are missing from the \"engines/\" directory.");
713 else if (!isAnyMasterEnabled())
714 message += tr(
"No master servers are enabled in the \"Query\" menu.");
716 message += tr(
"Unknown error occurred.");
719 QMessageBox::warning(
this, tr(
"Doomseeker - refresh problem"), message);
723 d->bTotalRefreshInProcess =
true;
724 d->autoRefreshTimer.stop();
725 gLog << tr(
"Total refresh initialized!");
730 d->serverList->removeNonSpecialServers();
731 refreshServersOnList();
733 if (!isAnyMasterEnabled() && !d->serverList->hasAtLeastOneServer())
735 gLog << tr(
"Warning: No master servers were enabled for this refresh. "
736 "Check your Query menu or \"engines/\" directory.");
739 d->masterManager->clearServers();
740 for (
int i = 0; i < d->masterManager->numMasters(); ++i)
745 gRefresher->registerMaster(pMaster);
749 bool MainWindow::hasCustomServers()
const
751 CustomServers *customServers = d->masterManager->customServs();
752 return customServers->numServers() > 0;
757 const unsigned MIN_DELAY = 30;
758 const unsigned MAX_DELAY = 3600;
760 bool bEnabled = gConfig.doomseeker.bQueryAutoRefreshEnabled;
763 d->autoRefreshTimer.stop();
768 unsigned &delay = gConfig.doomseeker.queryAutoRefreshEverySeconds;
771 if (delay < MIN_DELAY)
773 else if (delay > MAX_DELAY)
776 unsigned delayMs = delay * 1000;
778 d->autoRefreshTimer.setSingleShot(
false);
779 d->autoRefreshTimer.start(delayMs);
783 void MainWindow::initAutoUpdaterWidgets()
785 static const int FILE_BAR_WIDTH = 50;
786 static const int OVERALL_BAR_WIDTH = 180;
788 d->autoUpdaterStatusBarWidget =
new QWidget(statusBar());
789 d->autoUpdaterStatusBarWidget->setLayout(
new QHBoxLayout(d->autoUpdaterStatusBarWidget));
790 d->autoUpdaterStatusBarWidget->layout()->setContentsMargins(QMargins(0, 0, 0, 0));
791 statusBar()->addPermanentWidget(d->autoUpdaterStatusBarWidget);
792 d->autoUpdaterStatusBarWidget->hide();
794 d->autoUpdaterLabel =
new QLabel(d->autoUpdaterStatusBarWidget);
795 d->autoUpdaterLabel->setText(tr(
"Auto Updater:"));
796 d->autoUpdaterStatusBarWidget->layout()->addWidget(d->autoUpdaterLabel);
798 d->autoUpdaterFileProgressBar = mkStdProgressBarForStatusBar();
799 d->autoUpdaterFileProgressBar->setFormat(
"%p%");
800 d->autoUpdaterFileProgressBar->setMaximumWidth(FILE_BAR_WIDTH);
801 d->autoUpdaterFileProgressBar->setMinimumWidth(FILE_BAR_WIDTH);
802 d->autoUpdaterStatusBarWidget->layout()->addWidget(d->autoUpdaterFileProgressBar);
804 d->autoUpdaterOverallProgressBar = mkStdProgressBarForStatusBar();
805 d->autoUpdaterOverallProgressBar->setMaximumWidth(OVERALL_BAR_WIDTH);
806 d->autoUpdaterOverallProgressBar->setMinimumWidth(OVERALL_BAR_WIDTH);
807 d->autoUpdaterStatusBarWidget->layout()->addWidget(d->autoUpdaterOverallProgressBar);
809 d->autoUpdaterAbortButton =
new QPushButton(statusBar());
810 d->autoUpdaterAbortButton->setToolTip(tr(
"Abort update."));
811 d->autoUpdaterAbortButton->setIcon(QIcon(
":/icons/x.png"));
812 this->connect(d->autoUpdaterAbortButton, SIGNAL(clicked()),
813 SLOT(abortAutoUpdater()));
814 d->autoUpdaterStatusBarWidget->layout()->addWidget(d->autoUpdaterAbortButton);
817 void MainWindow::initIP2CUpdater()
819 static const int PROGRESSBAR_WIDTH = 220;
821 d->ip2cUpdateProgressBar = mkStdProgressBarForStatusBar();
822 d->ip2cUpdateProgressBar->setFormat(tr(
"IP2C Update"));
823 d->ip2cUpdateProgressBar->hide();
824 d->ip2cUpdateProgressBar->setMaximumWidth(PROGRESSBAR_WIDTH);
825 d->ip2cUpdateProgressBar->setMinimumWidth(PROGRESSBAR_WIDTH);
826 statusBar()->addPermanentWidget(d->ip2cUpdateProgressBar);
829 void MainWindow::initIRCDock()
831 d->ircDock =
new IRCDock(
this);
832 d->menuView->addAction(d->ircDock->toggleViewAction());
833 d->ircDock->toggleViewAction()->setText(tr(
"&IRC"));
834 d->ircDock->toggleViewAction()->setShortcut(tr(
"CTRL+I"));
836 this->addDockWidget(Qt::BottomDockWidgetArea, d->ircDock);
840 this->d->ircDock->setVisible(
true);
841 this->d->ircDock->performNetworkAutojoins();
845 void MainWindow::initLogDock()
847 d->logDock =
new LogDock(
this);
848 d->menuView->addAction(d->logDock->toggleViewAction());
849 d->logDock->toggleViewAction()->setText(tr(
"&Log"));
850 d->logDock->toggleViewAction()->setShortcut(tr(
"CTRL+L"));
852 this->addDockWidget(Qt::BottomDockWidgetArea, d->logDock);
854 connect(&gLog, SIGNAL(newEntry(
const QString&)), d->logDock, SLOT(appendLogEntry(
const QString&)));
857 d->logDock->appendLogEntry(gLog.content());
860 void MainWindow::initMainDock()
862 setDockNestingEnabled(
true);
866 d->mainDock =
new QDockWidget(tr(
"Servers"));
867 d->mainDock->setTitleBarWidget(
new QWidget(
this));
868 d->mainDock->setObjectName(
"ServerList");
869 d->mainDock->setFeatures(QDockWidget::NoDockWidgetFeatures);
870 d->mainDock->setWidget(centralWidget());
871 setCentralWidget(
nullptr);
872 addDockWidget(Qt::RightDockWidgetArea, d->mainDock);
875 void MainWindow::initServerDetailsDock()
878 d->menuView->addAction(d->detailsDock->toggleViewAction());
879 d->detailsDock->toggleViewAction()->setText(tr(
"Server &Details"));
880 d->detailsDock->toggleViewAction()->setShortcut(tr(
"CTRL+D"));
881 d->detailsDock->hide();
882 addDockWidget(Qt::BottomDockWidgetArea, d->detailsDock);
884 d->detailsDock->connect(d->serverList, SIGNAL(serversSelected(QList<ServerPtr>&)), SLOT(displaySelection(QList<ServerPtr> &)));
887 void MainWindow::initServerFilterDock()
891 d->menuView->addAction(d->serverFilterDock->toggleViewAction());
892 d->serverFilterDock->hide();
893 this->addDockWidget(Qt::RightDockWidgetArea, d->serverFilterDock);
898 bool isEnabled = gConfig.doomseeker.bUseTrayIcon;
899 if (!isEnabled || !QSystemTrayIcon::isSystemTrayAvailable())
901 if (d->trayIcon !=
nullptr)
904 d->trayIcon =
nullptr;
907 if (d->trayIconMenu !=
nullptr)
909 delete d->trayIconMenu;
910 d->trayIconMenu =
nullptr;
913 else if (d->trayIcon ==
nullptr)
916 d->trayIconMenu =
new QMenu(
this);
917 trayAction = d->trayIconMenu->addAction(
"Exit");
918 connect(trayAction, SIGNAL(triggered()),
this, SLOT(quitProgram()));
921 d->trayIcon =
new QSystemTrayIcon(
this);
922 connect(d->trayIcon, SIGNAL(activated(QSystemTrayIcon::ActivationReason)),
this, SLOT(trayIcon_activated(QSystemTrayIcon::ActivationReason)));
924 updateTrayIconTooltipAndLogTotalRefresh();
926 d->trayIcon->setContextMenu(d->trayIconMenu);
927 d->trayIcon->setIcon(QIcon(
":/icon.png"));
928 d->trayIcon->setVisible(
true);
932 void MainWindow::ip2cDownloadProgress(qint64 current, qint64 max)
934 d->ip2cUpdateProgressBar->setMaximum(max);
935 d->ip2cUpdateProgressBar->setValue(current);
938 void MainWindow::ip2cJobsFinished()
940 d->menuActionUpdateIP2C->setEnabled(
true);
941 d->serverList->updateCountryFlags();
942 d->ip2cUpdateProgressBar->hide();
944 if (d->ip2cLoader !=
nullptr)
946 delete d->ip2cLoader;
947 d->ip2cLoader =
nullptr;
951 void MainWindow::ip2cStartUpdate()
953 if (d->ip2cLoader !=
nullptr)
959 d->menuActionUpdateIP2C->setEnabled(
false);
960 d->ip2cUpdateProgressBar->show();
964 d->ip2cLoader->update();
969 return hasCustomServers() || isAnyMasterEnabled();
972 bool MainWindow::isAnyMasterEnabled()
const
974 for (
int i = 0; i < d->masterManager->numMasters(); ++i)
985 bool MainWindow::isEffectivelyActiveWindow()
const
987 return this->isActiveWindow() || DoomseekerConfigurationDialog::isOpen();
990 void MainWindow::masterManagerMessages(
MasterClient *pSender,
const QString &title,
const QString &content,
bool isError)
993 QString message = tr(
"Master server for %1: %2").arg(pSender->
plugin()->data()->name).arg(content);
997 message = tr(
"Error: %1").arg(message);
998 statusBar()->showMessage(message);
1004 void MainWindow::masterManagerMessagesImportant(
MasterClient *pSender,
const Message &objMessage)
1006 QString strFullMessage = tr(
"%1: %2")
1007 .arg(pSender->
plugin()->data()->name)
1009 d->importantMessagesWidget->addMessage(strFullMessage, objMessage.
timestamp());
1012 void MainWindow::menuBuddies()
1014 d->buddiesList->setVisible(!d->buddiesList->isVisible());
1017 void MainWindow::menuCreateServer()
1021 dialog->setWindowIcon(this->windowIcon());
1025 void MainWindow::menuHelpAbout()
1028 d->autoRefreshTimer.stop();
1033 void MainWindow::menuHelpHelp()
1037 QMessageBox::critical(
this, tr(
"Help error"), tr(
"No help found."), QMessageBox::Ok, QMessageBox::Ok);
1041 bool bSuccess = QDesktopServices::openUrl(HELP_SITE_URL);
1045 QMessageBox::critical(
this, tr(
"Help error"), tr(
"Failed to open URL:\n%1").arg(HELP_SITE_URL), QMessageBox::Ok, QMessageBox::Ok);
1050 void MainWindow::menuIRCOptions()
1053 dialog.initOptionsList();
1056 if (d->ircDock !=
nullptr)
1058 d->ircDock->applyAppearanceSettings();
1063 d->ircDock->sounds().loadFromConfig();
1067 void MainWindow::menuLog()
1069 d->logDock->setVisible(!d->logDock->isVisible());
1072 void MainWindow::menuManageDemos()
1075 dm.setWindowIcon(this->windowIcon());
1079 void MainWindow::menuOptionsConfigure()
1081 DoomseekerConfigurationDialog::openConfiguration(
this);
1084 void MainWindow::menuRecordDemo()
1086 gConfig.doomseeker.bRecordDemo = d->menuActionRecordDemo->isChecked();
1089 void MainWindow::menuUpdateIP2C()
1093 connect(&updateBox, SIGNAL(accepted()),
this, SLOT(ip2cStartUpdate()));
1098 void MainWindow::menuViewIRC()
1100 d->ircDock->setVisible(!d->ircDock->isVisible());
1103 void MainWindow::menuWadSeeker()
1105 if (gWadseekerShow->checkWadseekerValidity(
this))
1108 wadseeker->setAttribute(Qt::WA_DeleteOnClose);
1113 QProgressBar *MainWindow::mkStdProgressBarForStatusBar()
1115 auto pBar =
new QProgressBar(statusBar());
1116 pBar->setAlignment(Qt::AlignCenter);
1117 pBar->setTextVisible(
true);
1118 pBar->setSizePolicy(QSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed));
1122 void MainWindow::notifyFirstRun()
1125 QMessageBox::information(
nullptr, tr(
"Welcome to Doomseeker"),
1126 tr(
"Before you start browsing for servers, please ensure that Doomseeker is properly configured."));
1127 d->menuActionConfigure->trigger();
1130 void MainWindow::onAutoUpdaterDownloadAndInstallConfirmationRequest()
1132 d->updatesConfirmationWidget->show();
1135 void MainWindow::onAutoUpdaterFileProgress(qint64 bytesReceived, qint64 bytesTotal)
1137 d->autoUpdaterFileProgressBar->setValue(bytesReceived);
1138 d->autoUpdaterFileProgressBar->setMaximum(bytesTotal);
1141 void MainWindow::onAutoUpdaterFinish()
1143 showAndLogStatusMessage(tr(
"Program update detection & download finished with status: [%1] %2")
1144 .arg((
int)d->autoUpdater->errorCode()).arg(d->autoUpdater->errorString()));
1145 gConfig.autoUpdates.bPerformUpdateOnNextRun =
false;
1146 if (d->autoUpdater->errorCode() == AutoUpdater::EC_Ok)
1149 if (channel == *d->updateChannelOnUpdateStart)
1151 if (!d->autoUpdater->downloadedPackagesFilenames().isEmpty())
1153 gLog << tr(
"Updates will be installed on next program start.");
1154 d->updatesDownloadedWidget->show();
1155 gConfig.autoUpdates.bPerformUpdateOnNextRun =
true;
1159 gLog << tr(
"Update channel was changed during update process. Discarding update.");
1161 gConfig.saveToFile();
1162 d->autoUpdaterStatusBarWidget->hide();
1163 d->updatesConfirmationWidget->hide();
1164 d->autoUpdater->deleteLater();
1165 d->autoUpdater =
nullptr;
1168 void MainWindow::onAutoUpdaterOverallProgress(
int current,
int total,
1171 d->autoUpdaterOverallProgressBar->setValue(current);
1172 d->autoUpdaterOverallProgressBar->setMaximum(total);
1173 d->autoUpdaterOverallProgressBar->setFormat(msg);
1176 void MainWindow::postInitAppStartup()
1179 d->serverFilterDock->setFilterInfo(gConfig.serverFilter.info);
1185 if (gPlugins->numPlugins() > 0)
1187 bool bGettingServers =
false;
1188 bool queryOnStartup = gConfig.doomseeker.bQueryOnStartup;
1195 if (isAnyMasterEnabled())
1197 bGettingServers =
true;
1201 gLog << tr(
"Query on startup warning: No master servers are enabled in the Query menu.");
1206 if (!bGettingServers && hasCustomServers())
1211 refreshCustomServers();
1218 QString error = tr(
"Doomseeker was unable to find any plugin libraries.\n"
1219 "Although the application will still work it will not be possible "
1220 "to fetch any server info or launch any game.\n\n"
1221 "Please check if there are any files in \"engines/\" directory.\n"
1222 "To fix this problem you may try downloading Doomseeker "
1223 "again from the site specified in the Help|About box and reinstalling "
1225 QMessageBox::critical(
nullptr, tr(
"Doomseeker critical error"), error);
1229 QueryMenuAction *MainWindow::queryMenuActionForPlugin(
const EnginePlugin *plugin)
1231 if (plugin ==
nullptr)
1234 if (d->queryMenuPorts.contains(plugin))
1235 return d->queryMenuPorts[plugin];
1240 void MainWindow::quitProgram()
1242 d->bWantToQuit =
true;
1243 QApplication::closeAllWindows();
1246 void MainWindow::refreshCustomServers()
1248 for (
const ServerPtr &server : d->serverList->servers())
1250 if (server->isCustom())
1251 gRefresher->registerServer(server.data());
1255 void MainWindow::refreshServersOnList()
1257 for (
const ServerPtr &server : d->serverList->servers())
1259 gRefresher->registerServer(server.data());
1263 void MainWindow::refreshThreadBeginsWork()
1265 statusBar()->showMessage(tr(
"Querying..."));
1266 d->taskbarProgress->show();
1269 void MainWindow::refreshThreadEndsWork()
1271 d->toolBarGetServers->setEnabled(
true);
1273 d->serverList->cleanUpRightNow();
1274 statusBar()->showMessage(tr(
"Done"));
1275 updateTrayIconTooltipAndLogTotalRefresh();
1276 d->taskbarProgress->hide();
1278 if (d->bTotalRefreshInProcess)
1281 d->bTotalRefreshInProcess =
false;
1282 QList<ServerPtr> selectedServers = d->serverList->selectedServers();
1283 d->detailsDock->displaySelection(selectedServers);
1286 void MainWindow::restartAndInstallUpdatesNow()
1292 void MainWindow::runGame(
const ServerPtr &server)
1294 if (d->connectionHandler)
1295 delete d->connectionHandler;
1298 d->connectionHandler->run();
1303 assert(plugin !=
nullptr);
1305 QueryMenuAction *pAction = queryMenuActionForPlugin(plugin);
1306 if (pAction !=
nullptr)
1308 pAction->setChecked(bEnabled);
1309 if (plugin->data()->hasMasterClient())
1310 plugin->data()->masterClient->
setEnabled(bEnabled);
1311 if (plugin->data()->hasBroadcast())
1312 plugin->data()->broadcast->setEnabled(bEnabled);
1313 d->serversStatusesWidgets[plugin]->setMasterEnabledStatus(bEnabled);
1317 void MainWindow::serverAddedToList(
const ServerPtr &pServer)
1319 if (pServer->isKnown())
1321 const QString &gameMode = pServer->gameMode().
name();
1322 d->serverFilterDock->addGameModeToComboBox(gameMode);
1328 assert(this->d->updaterInstallerErrorCode == 0 &&
1329 "MainWindow::setDisplayUpdaterProcessFailure()");
1330 this->d->updaterInstallerErrorCode = errorCode;
1331 QTimer::singleShot(0,
this, SLOT(showUpdaterProcessErrorDialog()));
1336 assert(this->d->updaterInstallerErrorCode == 0 &&
1337 "MainWindow::setDisplayUpdateInstallerError()");
1338 this->d->updaterInstallerErrorCode = errorCode;
1339 QTimer::singleShot(0,
this, SLOT(showUpdateInstallErrorDialog()));
1342 void MainWindow::setupIcons()
1344 QStyle &style = *QApplication::style();
1347 d->menuActionQuit->setIcon(style.standardIcon(QStyle::SP_TitleBarCloseButton));
1350 d->menuActionHelp->setIcon(style.standardIcon(QStyle::SP_MessageBoxQuestion));
1351 d->menuActionAbout->setIcon(style.standardIcon(QStyle::SP_MessageBoxInformation));
1354 void MainWindow::setupToolBar()
1356 QToolBar *pToolBar =
new QToolBar(tr(
"Main Toolbar"),
this);
1357 pToolBar->setMovable(
false);
1358 pToolBar->setObjectName(
"Toolbar");
1361 d->toolBarGetServers =
new QAction(QIcon(
":/icons/refresh.png"), tr(
"Get Servers"), pToolBar);
1365 pToolBar->addAction(d->toolBarGetServers);
1368 pToolBar->addSeparator();
1369 pToolBar->addAction(d->menuActionCreateServer);
1370 pToolBar->addAction(d->menuActionWadseeker);
1373 pToolBar->addSeparator();
1374 pToolBar->addAction(d->menuActionManageDemos);
1375 pToolBar->addAction(d->menuActionRecordDemo);
1377 pToolBar->addSeparator();
1380 pToolBar->addAction(d->buddiesList->toggleViewAction());
1381 pToolBar->addAction(d->logDock->toggleViewAction());
1382 pToolBar->addAction(d->ircDock->toggleViewAction());
1383 pToolBar->addAction(d->serverFilterDock->toggleViewAction());
1384 pToolBar->addAction(d->detailsDock->toggleViewAction());
1387 QLineEdit *qs = d->serverFilterDock->createQuickSearch();
1388 qs->setSizePolicy(QSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed));
1389 qs->setMinimumWidth(175);
1390 qs->setMaximumWidth(175);
1392 QWidget *searchSeparator =
new QWidget();
1393 searchSeparator->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::MinimumExpanding);
1394 pToolBar->addWidget(searchSeparator);
1395 pToolBar->addWidget(
new QLabel(tr(
"Search:"), pToolBar));
1396 pToolBar->addWidget(qs);
1398 this->addToolBar(Qt::TopToolBarArea, pToolBar);
1399 setUnifiedTitleAndToolBarOnMac(
true);
1400 connect(pToolBar, SIGNAL(actionTriggered(QAction*)),
this, SLOT(toolBarAction(QAction*)));
1403 void MainWindow::showAndLogStatusMessage(
const QString &message)
1406 statusBar()->showMessage(message);
1409 void MainWindow::showEvent(QShowEvent *event)
1413 d->taskbarButton->setWindow(windowHandle());
1416 void MainWindow::showInstallFreedoomDialog()
1418 if (!d->freedoomDialog.isNull())
1420 d->freedoomDialog->activateWindow();
1424 dialog->setAttribute(Qt::WA_DeleteOnClose);
1426 d->freedoomDialog = dialog;
1429 void MainWindow::showProgramArgsHelp()
1432 dialog->setAttribute(Qt::WA_DeleteOnClose);
1436 void MainWindow::showServerJoinCommandLine(
const ServerPtr &server)
1440 GameDemo demo = gConfig.doomseeker.bRecordDemo ? GameDemo::Unmanaged : GameDemo::NoDemo;
1442 this->connect(builder, SIGNAL(commandLineBuildFinished()), SLOT(showServerJoinCommandLineOnBuilderFinished()));
1446 void MainWindow::showServerJoinCommandLineOnBuilderFinished()
1452 QString execPath = cli.
executable.absoluteFilePath();
1453 QStringList args = cli.
args;
1458 CopyTextDlg ctd(execPath +
" " + args.join(
" "), builder->server()->name(),
this);
1463 if (!builder->error().isEmpty())
1465 QMessageBox::critical(
this, tr(
"Doomseeker - show join command line"),
1466 tr(
"Command line cannot be built:\n%1").arg(builder->error()));
1469 builder->deleteLater();
1472 void MainWindow::showUpdaterProcessErrorDialog()
1474 QString explanation;
1475 if (this->d->updaterInstallerErrorCode != UpdateInstaller::PEC_GeneralFailure)
1477 QString errorCodeExplanation = UpdateInstaller::processErrorCodeToStr(
1479 explanation = tr(
"Update installation problem:\n%1").arg(errorCodeExplanation);
1482 explanation = tr(
"Update installation failed.");
1483 QMessageBox::critical(
this, tr(
"Doomseeker - Auto Update problem"),
1484 tr(
"%1\n\nRemaining updates have been discarded.").arg(explanation));
1487 void MainWindow::showUpdateInstallErrorDialog()
1489 QString error = UpdateInstaller::errorCodeToStr(
1491 QString msg = tr(
"Update install problem:\n%1\n\nRemaining updates have been discarded.").arg(error);
1492 QMessageBox::critical(
this, tr(
"Doomseeker - Auto Update problem"), msg);
1495 void MainWindow::stopAutoRefreshTimer()
1497 d->autoRefreshTimer.stop();
1502 QueryMenuAction *pAction = queryMenuActionForPlugin(plugin);
1503 assert(pAction !=
nullptr);
1508 void MainWindow::toolBarAction(QAction *pAction)
1510 if (pAction == d->toolBarGetServers)
1514 void MainWindow::trayIcon_activated(QSystemTrayIcon::ActivationReason reason)
1516 if (reason == QSystemTrayIcon::Trigger)
1518 if (isMinimized() || !isVisible())
1520 d->bWasMaximized ==
true ? showMaximized() : showNormal();
1523 else if (gConfig.doomseeker.bCloseToTrayIcon)
1530 void MainWindow::updateDynamicAppearance()
1532 d->tableServers->setShowGrid(gConfig.doomseeker.bDrawGridInServerTable);
1533 d->serverList->redraw();
1534 d->serverList->cleanUpForce();
1539 void MainWindow::updateMasterAddresses()
1541 for (
int i = 0; i < d->masterManager->numMasters(); i++)
1542 (*d->masterManager)[i]->updateAddress();
1547 d->serverList->applyFilter(filterInfo);
1556 count += status->count();
1561 void MainWindow::updateRefreshProgress()
1564 d->taskbarProgress->setMaximum(count.numServers);
1565 d->taskbarProgress->setValue(count.numServers - count.numRefreshing);
1566 updateTrayIconTooltip(count);
1571 if (d->trayIcon !=
nullptr)
1574 tip += tr(
"Generic servers: %1\n").arg(count.numGenericServers);
1575 tip += tr(
"Custom servers: %1\n").arg(count.numCustomServers);
1576 tip += tr(
"LAN servers: %1\n").arg(count.numLanServers);
1577 tip += tr(
"Human players: %1").arg(count.numHumanPlayers);
1578 d->trayIcon->setToolTip(tip);
1582 void MainWindow::updateTrayIconTooltipAndLogTotalRefresh()
1585 updateTrayIconTooltip(count);
1587 if (d->bTotalRefreshInProcess)
1589 gLog << tr(
"Finished refreshing. Servers on the list: %1 "
1590 "(+%2 custom, +%3 LAN). Players: %4.")
1591 .arg(count.numGenericServers).arg(count.numCustomServers)
1592 .arg(count.numLanServers).arg(count.numHumanPlayers);