23 #include "application.h"
24 #include "apprunner.h"
25 #include "commandline.h"
26 #include "configuration/doomseekerconfig.h"
27 #include "configuration/gameconfigerrorbox.h"
28 #include "configuration/queryspeed.h"
29 #include "connectionhandler.h"
30 #include "customservers.h"
31 #include "datapaths.h"
32 #include "doomseekerfilepaths.h"
33 #include "fileutils.h"
35 #include "gui/aboutdialog.h"
36 #include "gui/checkwadsdlg.h"
37 #include "gui/configuration/doomseekerconfigurationdialog.h"
38 #include "gui/configuration/irc/ircconfigurationdialog.h"
39 #include "gui/copytextdlg.h"
40 #include "gui/createserverdialog.h"
41 #include "gui/demomanager.h"
42 #include "gui/dockBuddiesList.h"
43 #include "gui/freedoomdialog.h"
44 #include "gui/helpers/playersdiagram.h"
45 #include "gui/helpers/taskbarbutton.h"
46 #include "gui/helpers/taskbarprogress.h"
47 #include "gui/ip2cupdatebox.h"
48 #include "gui/irc/ircdock.h"
49 #include "gui/irc/ircsounds.h"
50 #include "gui/logdock.h"
51 #include "gui/mainwindow.h"
52 #include "gui/models/serverlistmodel.h"
53 #include "gui/programargshelpdialog.h"
54 #include "gui/serverdetailsdock.h"
55 #include "gui/serverfilterdock.h"
56 #include "gui/serverlist.h"
57 #include "gui/wadseekerinterface.h"
58 #include "gui/wadseekershow.h"
59 #include "gui/widgets/serversstatuswidget.h"
60 #include "ip2c/ip2c.h"
61 #include "ip2c/ip2cloader.h"
62 #include "irc/configuration/chatnetworkscfg.h"
63 #include "irc/configuration/ircconfig.h"
64 #include "joincommandlinebuilder.h"
67 #include "pathfinder/pathfinder.h"
68 #include "pathfinder/wadpathfinder.h"
69 #include "plugins/engineplugin.h"
70 #include "plugins/pluginloader.h"
71 #include "refresher/refresher.h"
72 #include "serverapi/broadcast.h"
73 #include "serverapi/broadcastmanager.h"
74 #include "serverapi/gameclientrunner.h"
75 #include "serverapi/mastermanager.h"
76 #include "serverapi/message.h"
77 #include "serverapi/server.h"
78 #include "serverapi/serverlistcounttracker.h"
79 #include "strings.hpp"
80 #include "ui_mainwindow.h"
81 #include "updater/autoupdater.h"
82 #include "updater/updatechannel.h"
83 #include "updater/updateinstaller.h"
84 #include "updater/updatepackage.h"
85 #include "wadseeker/entities/checksum.h"
88 #include <QApplication>
89 #include <QDesktopServices>
90 #include <QDockWidget>
92 #include <QHeaderView>
95 #include <QMessageBox>
96 #include <QMessageBox>
98 #include <QProgressBar>
99 #include <QSizePolicy>
102 const QString MainWindow::HELP_SITE_URL =
"https://doomseeker.drdteam.org/help";
111 class QueryMenuAction :
public QAction
117 this->pPlugin = plugin;
119 if (plugin !=
nullptr)
121 connect(
this, SIGNAL(toggled(
bool)), plugin->data()->masterClient,
122 SLOT(setEnabled(
bool)));
123 connect(
this, SIGNAL(toggled(
bool)), statusWidget, SLOT(setMasterEnabledStatus(
bool)));
136 DClass<MainWindow> :
public Ui::MainWindowWnd
139 PrivData() : bTotalRefreshInProcess(false), buddiesList(nullptr),
140 bWasMaximized(false), bWantToQuit(false), logDock(nullptr),
141 masterManager(nullptr), trayIcon(nullptr), trayIconMenu(nullptr)
145 QApplication *application;
146 QTimer autoRefreshTimer;
149 QWidget *autoUpdaterStatusBarWidget;
150 QPushButton *autoUpdaterAbortButton;
151 QLabel *autoUpdaterLabel;
152 QProgressBar *autoUpdaterFileProgressBar;
153 QProgressBar *autoUpdaterOverallProgressBar;
159 bool bTotalRefreshInProcess;
177 QProgressBar *ip2cUpdateProgressBar;
181 QPointer<FreedoomDialog> freedoomDialog;
186 QHash<const EnginePlugin *, QueryMenuAction *> queryMenuPorts;
187 QHash<const EnginePlugin *, ServersStatusWidget *> serversStatusesWidgets;
188 QAction *toolBarGetServers;
189 QSystemTrayIcon *trayIcon;
193 int updaterInstallerErrorCode;
196 QDockWidget *mainDock;
201 QList<QSharedPointer<ImportantMessagesWidget::Handler>> masterMessages;
208 d->autoUpdater =
nullptr;
209 d->mainDock =
nullptr;
210 d->connectionHandler =
nullptr;
212 d->updaterInstallerErrorCode = 0;
214 d->application = application;
216 this->setAttribute(Qt::WA_DeleteOnClose,
true);
220 d->taskbarProgress = d->taskbarButton->progress();
224 initAutoUpdaterWidgets();
226 d->updatesConfirmationWidget->hide();
227 d->updatesDownloadedWidget->hide();
230 #ifndef WITH_AUTOUPDATES
231 d->menuActionCheckForUpdates->setVisible(
false);
238 d->menuView->addAction(d->buddiesList->toggleViewAction());
239 d->buddiesList->toggleViewAction()->setText(MainWindow::tr(
"&Buddies"));
240 d->buddiesList->toggleViewAction()->setShortcut(MainWindow::tr(
"CTRL+B"));
242 connect(d->buddiesList, SIGNAL(joinServer(ServerPtr)),
this, SLOT(runGame(ServerPtr)));
243 d->buddiesList->hide();
244 this->addDockWidget(Qt::LeftDockWidgetArea, d->buddiesList);
247 initServerFilterDock();
249 splitDockWidget(d->mainDock, d->serverFilterDock, Qt::Horizontal);
252 d->serverList =
new ServerList(d->tableServers,
this);
256 d->serverList->connect(d->broadcastManager,
257 SIGNAL(newServerDetected(ServerPtr,
int)), SLOT(registerServer(ServerPtr)));
258 d->serverList->connect(d->broadcastManager,
259 SIGNAL(serverLost(ServerPtr)), SLOT(removeServer(ServerPtr)));
261 initServerDetailsDock();
262 tabifyDockWidget(d->ircDock, d->detailsDock);
265 d->menuActionRecordDemo->setChecked(gConfig.doomseeker.bRecordDemo);
269 d->masterManager->setBroadcastManager(d->broadcastManager);
270 d->buddiesList->scan(d->masterManager);
271 connect(d->masterManager, SIGNAL(masterMessage(
MasterClient*,
const QString&,
const QString&,
bool)),
272 this, SLOT(masterManagerMessages(
MasterClient*,
const QString&,
const QString&,
bool)));
277 fillQueryMenu(d->masterManager);
280 QList<ServerPtr> customServers = d->masterManager->customServs()->readConfig();
281 for (ServerPtr server : customServers)
283 d->serverList->registerServer(server);
286 setWindowIcon(Application::icon());
289 initAutoRefreshTimer();
290 connect(&d->autoRefreshTimer, SIGNAL(timeout()),
this, SLOT(autoRefreshTimer_timeout()));
301 d->menuActionUpdateIP2C->setEnabled(
false);
302 d->ip2cLoader =
new IP2CLoader(*IP2C::instance());
304 d->ip2cLoader->load();
306 restoreState(QByteArray::fromBase64(gConfig.doomseeker.mainWindowState.toUtf8()));
307 restoreGeometry(gConfig.doomseeker.mainWindowGeometry);
311 QTimer::singleShot(1,
this, SLOT(postInitAppStartup()));
314 MainWindow::~MainWindow()
317 gConfig.doomseeker.mainWindowGeometry = saveGeometry();
318 gConfig.doomseeker.mainWindowState = saveState().toBase64();
320 for (
auto handler : d->masterMessages)
322 d->masterMessages.clear();
324 if (d->updateChannelOnUpdateStart !=
nullptr)
325 delete d->updateChannelOnUpdateStart;
326 if (d->autoUpdater !=
nullptr)
328 d->autoUpdater->disconnect();
329 delete d->autoUpdater;
331 if (d->connectionHandler)
332 delete d->connectionHandler;
334 QList<QAction *> menuQueryActions = d->menuQuery->actions();
335 QList<QAction *>::iterator it;
336 for (QAction *action : menuQueryActions)
338 QString pluginName = action->text();
340 if (!pluginName.isEmpty())
342 IniSection pluginConfig = gConfig.iniSectionForPlugin(pluginName);
343 pluginConfig[
"Query"] = action->isChecked();
347 if (d->trayIcon !=
nullptr)
349 d->trayIcon->setVisible(
false);
351 d->trayIcon =
nullptr;
354 if (d->trayIconMenu !=
nullptr)
356 delete d->trayIconMenu;
357 d->trayIconMenu =
nullptr;
360 delete d->serverList;
362 if (d->masterManager !=
nullptr)
363 delete d->masterManager;
365 if (d->ip2cLoader !=
nullptr)
366 delete d->ip2cLoader;
369 void MainWindow::abortAutoUpdater()
371 if (d->autoUpdater !=
nullptr)
372 d->autoUpdater->abort();
375 void MainWindow::autoRefreshTimer_timeout()
377 if (gConfig.doomseeker.bQueryAutoRefreshDontIfActive && !isMinimized())
379 if (QApplication::activeWindow() !=
nullptr)
386 void MainWindow::blockRefreshButtons()
388 d->toolBarGetServers->setEnabled(
false);
393 return d->buddiesList;
396 void MainWindow::changeEvent(QEvent *event)
398 if (event->type() == QEvent::ActivationChange && isActiveWindow() && !isMinimized() && !isHidden())
400 d->serverList->cleanUp();
403 QMainWindow::changeEvent(event);
406 void MainWindow::checkForUpdates(
bool bUserTriggered)
408 if (d->autoUpdater !=
nullptr)
410 if (d->autoUpdater->isRunning())
412 QMessageBox::warning(
this, tr(
"Doomseeker - Auto Update"),
413 tr(
"Update is already in progress."));
418 delete d->autoUpdater;
419 d->autoUpdater =
nullptr;
422 gLog << tr(
"Removing old update packages from local temporary space.");
423 QStringList removeFilter(QString(
"%1*").arg(DataPaths::UPDATE_PACKAGE_FILENAME_PREFIX));
427 showAndLogStatusMessage(tr(
"Checking for updates..."));
429 this->connect(d->autoUpdater, SIGNAL(statusMessage(QString)),
430 SLOT(showAndLogStatusMessage(QString)));
431 this->connect(d->autoUpdater, SIGNAL(finished()),
432 SLOT(onAutoUpdaterFinish()));
433 this->connect(d->autoUpdater, SIGNAL(downloadAndInstallConfirmationRequested()),
434 SLOT(onAutoUpdaterDownloadAndInstallConfirmationRequest()));
435 this->connect(d->autoUpdater, SIGNAL(overallProgress(
int,
int,
const QString&)),
436 SLOT(onAutoUpdaterOverallProgress(
int,
int,
const QString&)));
437 this->connect(d->autoUpdater, SIGNAL(packageDownloadProgress(qint64,qint64)),
438 SLOT(onAutoUpdaterFileProgress(qint64,qint64)));
440 QMap<QString, QList<QString> > ignoredPackagesRevisions;
443 for (
const QString &package : gConfig.autoUpdates.lastKnownUpdateRevisions.keys())
445 QString revision = gConfig.autoUpdates.lastKnownUpdateRevisions[package];
448 ignoredPackagesRevisions.insert(package, list);
451 d->autoUpdater->setIgnoreRevisions(ignoredPackagesRevisions);
454 d->autoUpdater->setChannel(channel);
455 *d->updateChannelOnUpdateStart = channel;
457 bool bRequireConfirmation =
true;
460 bRequireConfirmation = (gConfig.autoUpdates.updateMode
463 d->autoUpdater->setRequireDownloadAndInstallConfirmation(bRequireConfirmation);
464 d->autoUpdaterStatusBarWidget->show();
465 d->autoUpdater->start();
471 const bool bUserTriggered =
true;
472 checkForUpdates(!bUserTriggered);
477 const bool bUserTriggered =
true;
478 checkForUpdates(bUserTriggered);
481 void MainWindow::closeEvent(QCloseEvent *event)
486 if (d->trayIcon !=
nullptr && gConfig.doomseeker.bCloseToTrayIcon && !d->bWantToQuit)
488 d->bWasMaximized = isMaximized();
496 void MainWindow::confirmUpdateInstallation()
498 assert(d->autoUpdater !=
nullptr &&
"MainWindow::confirmUpdateInstallation()");
499 d->updatesConfirmationWidget->hide();
500 d->autoUpdater->confirmDownloadAndInstall();
503 void MainWindow::connectIP2CLoader()
505 this->connect(d->ip2cLoader, SIGNAL(finished()), SLOT(ip2cJobsFinished()));
506 this->connect(d->ip2cLoader, SIGNAL(downloadProgress(qint64,qint64)),
507 SLOT(ip2cDownloadProgress(qint64,qint64)));
510 void MainWindow::discardUpdates()
512 assert(d->autoUpdater !=
nullptr &&
"MainWindow::confirmUpdateInstallation()");
513 d->updatesConfirmationWidget->hide();
516 const QList<UpdatePackage> &pkgList = d->autoUpdater->newUpdatePackages();
519 gConfig.autoUpdates.lastKnownUpdateRevisions.insert(pkg.name, pkg.revision);
521 d->autoUpdater->abort();
527 connect(gRefresher, SIGNAL(block()),
this, SLOT(blockRefreshButtons()));
528 connect(gRefresher, SIGNAL(finishedQueryingMaster(
MasterClient*)),
this, SLOT(finishedQueryingMaster(
MasterClient*)));
529 connect(gRefresher, SIGNAL(sleepingModeEnter()),
this, SLOT(refreshThreadEndsWork()));
530 connect(gRefresher, SIGNAL(sleepingModeEnter()), d->buddiesList, SLOT(scan()));
531 connect(gRefresher, SIGNAL(sleepingModeExit()),
this, SLOT(refreshThreadBeginsWork()));
534 connect(d->menuActionAbout, SIGNAL(triggered()),
this, SLOT(menuHelpAbout()));
535 connect(d->menuActionAboutQt, SIGNAL(triggered()), d->application, SLOT(aboutQt()));
536 connect(d->menuActionBuddies, SIGNAL(triggered()),
this, SLOT(menuBuddies()));
537 connect(d->menuActionConfigure, SIGNAL(triggered()),
this, SLOT(menuOptionsConfigure()));
538 connect(d->menuActionCreateServer, SIGNAL(triggered()),
this, SLOT(menuCreateServer()));
539 connect(d->menuActionHelp, SIGNAL(triggered()),
this, SLOT (menuHelpHelp()));
540 connect(d->menuActionIRCOptions, SIGNAL(triggered()),
this, SLOT(menuIRCOptions()));
541 connect(d->menuActionLog, SIGNAL(triggered()),
this, SLOT(menuLog()));
542 connect(d->menuActionManageDemos, SIGNAL(triggered()),
this, SLOT(menuManageDemos()));
543 connect(d->menuActionRecordDemo, SIGNAL(triggered()),
this, SLOT(menuRecordDemo()));
544 connect(d->menuActionUpdateIP2C, SIGNAL(triggered()),
this, SLOT(menuUpdateIP2C()));
545 connect(d->menuActionQuit, SIGNAL(triggered()),
this, SLOT(quitProgram()));
546 connect(d->menuActionViewIRC, SIGNAL(triggered()),
this, SLOT(menuViewIRC()));
547 connect(d->menuActionWadseeker, SIGNAL(triggered()),
this, SLOT(menuWadSeeker()));
552 connect(d->serverList, SIGNAL(serverDoubleClicked(ServerPtr)),
this, SLOT(runGame(ServerPtr)));
553 connect(d->serverList, SIGNAL(displayServerJoinCommandLine(
const ServerPtr&)),
this, SLOT(showServerJoinCommandLine(
const ServerPtr&)));
554 connect(d->serverList, SIGNAL(findMissingWADs(
const ServerPtr&)),
this, SLOT(findMissingWADs(
const ServerPtr&)));
555 connect(d->serverList, SIGNAL(serverInfoUpdated(ServerPtr)),
this, SLOT(serverAddedToList(ServerPtr)));
556 connect(d->buddiesList, SIGNAL(scanCompleted()), d->serverList, SLOT(redraw()));
563 for (
unsigned i = 0; i < gPlugins->numPlugins(); ++i)
566 if (!plugin->data()->hasMasterClient() && !plugin->data()->hasBroadcast())
569 if (plugin->data()->hasMasterClient())
571 MasterClient *pMasterClient = plugin->data()->masterClient;
572 pMasterClient->updateAddress();
573 masterManager->addMaster(pMasterClient);
576 if (plugin->data()->hasBroadcast())
577 d->broadcastManager->registerPlugin(plugin);
581 d->serversStatusesWidgets.insert(plugin, statusWidget);
583 this->connect(statusWidget, SIGNAL(clicked(
const EnginePlugin*)),
585 this->connect(statusWidget, SIGNAL(counterUpdated()),
586 SLOT(updateRefreshProgress()));
588 statusBar()->addPermanentWidget(statusWidget);
590 QString name = gPlugins->info(i)->data()->name;
591 auto query =
new QueryMenuAction(plugin, statusWidget, d->menuQuery);
592 d->queryMenuPorts.insert(plugin, query);
594 d->menuQuery->addAction(query);
596 query->setCheckable(
true);
597 query->setIcon(plugin->icon());
598 query->setText(name);
600 IniSection pluginConfig = gConfig.iniSectionForPlugin(name);
604 bool enabled = pluginConfig[
"Query"];
616 void MainWindow::findMissingWADs(
const ServerPtr &server)
619 QList<PWad> wads = server->wads();
620 PathFinder pathFinder = server->wadPathFinder();
621 QList<PWad> missingWads;
622 QList<PWad> incompatibleWads;
625 checkWadsDlg->addWads(wads);
626 const CheckResult checkResults = checkWadsDlg->checkWads();
628 for (
const PWad &wad : checkResults.missingWads)
632 incompatibleWads << checkResults.incompatibleWads;
634 if (missingWads.isEmpty() && incompatibleWads.isEmpty())
636 QMessageBox::information(
this, tr(
"All WADs found"), tr(
"All of the WADs used by this server are present."));
641 dialog.setAllowIgnore(
false);
642 if (dialog.exec() == QDialog::Accepted && dialog.decision() == MissingWadsDialog::Install)
644 if (!gWadseekerShow->checkWadseekerValidity(
this))
647 wadseeker->setCustomSites(server->allWebSites());
648 wadseeker->
setWads(dialog.filesToDownload());
649 wadseeker->setAttribute(Qt::WA_DeleteOnClose);
657 updateMasterAddresses();
658 gRefresher->setDelayBetweenResends(gConfig.doomseeker.querySpeed().delayBetweenSingleServerAttempts);
661 if (configDialog.wasAppearanceChanged())
663 updateDynamicAppearance();
668 if (configDialog.isRestartNeeded())
670 QString warningRestartNeeded = tr(
"Doomseeker needs to be restarted for some changes to be applied.");
671 d->importantMessagesWidget->addMessage(warningRestartNeeded);
675 if (lookupHostsChanged)
676 d->serverList->lookupHosts();
679 if (configDialog.customServersChanged())
681 d->serverList->removeCustomServers();
682 QList<ServerPtr> servers = d->masterManager->customServs()->readConfig();
683 for (ServerPtr server : servers)
685 d->serverList->registerServer(server);
687 refreshCustomServers();
691 void MainWindow::finishedQueryingMaster(
MasterClient *master)
693 if (master ==
nullptr)
696 for (
int i = 0; i < master->numServers(); i++)
697 d->serverList->registerServer((*master)[i]);
700 void MainWindow::fixIconsDpi()
703 QIcon icon(
":/icons/exclamation_16.png");
704 d->lblExclamation1->setPixmap(icon.pixmap(16));
705 d->lblExclamation2->setPixmap(icon.pixmap(16));
708 void MainWindow::getServers()
713 QString message = tr(
"Doomseeker is unable to proceed with the refresh"
714 " operation because the following problem has occurred:\n\n");
716 if (gPlugins->numPlugins() == 0)
717 message += tr(
"Plugins are missing from the \"engines/\" directory.");
718 else if (!isAnyMasterEnabled())
719 message += tr(
"No master servers are enabled in the \"Query\" menu.");
721 message += tr(
"Unknown error occurred.");
724 QMessageBox::warning(
this, tr(
"Doomseeker - refresh problem"), message);
728 d->bTotalRefreshInProcess =
true;
729 d->autoRefreshTimer.stop();
730 gLog << tr(
"Total refresh initialized!");
735 d->serverList->removeNonSpecialServers();
736 refreshServersOnList();
738 if (!isAnyMasterEnabled() && !d->serverList->hasAtLeastOneServer())
740 gLog << tr(
"Warning: No master servers were enabled for this refresh. "
741 "Check your Query menu or \"engines/\" directory.");
744 for (
auto handler : d->masterMessages)
746 d->masterMessages.clear();
747 d->masterManager->clearServers();
748 for (
int i = 0; i < d->masterManager->numMasters(); ++i)
753 gRefresher->registerMaster(pMaster);
757 bool MainWindow::hasCustomServers()
const
759 CustomServers *customServers = d->masterManager->customServs();
760 return customServers->numServers() > 0;
765 const unsigned MIN_DELAY = 30;
766 const unsigned MAX_DELAY = 3600;
768 bool bEnabled = gConfig.doomseeker.bQueryAutoRefreshEnabled;
771 d->autoRefreshTimer.stop();
776 unsigned &delay = gConfig.doomseeker.queryAutoRefreshEverySeconds;
779 if (delay < MIN_DELAY)
781 else if (delay > MAX_DELAY)
784 unsigned delayMs = delay * 1000;
786 d->autoRefreshTimer.setSingleShot(
false);
787 d->autoRefreshTimer.start(delayMs);
791 void MainWindow::initAutoUpdaterWidgets()
793 static const int FILE_BAR_WIDTH = 50;
794 static const int OVERALL_BAR_WIDTH = 180;
796 d->autoUpdaterStatusBarWidget =
new QWidget(statusBar());
797 d->autoUpdaterStatusBarWidget->setLayout(
new QHBoxLayout(d->autoUpdaterStatusBarWidget));
798 d->autoUpdaterStatusBarWidget->layout()->setContentsMargins(QMargins(0, 0, 0, 0));
799 statusBar()->addPermanentWidget(d->autoUpdaterStatusBarWidget);
800 d->autoUpdaterStatusBarWidget->hide();
802 d->autoUpdaterLabel =
new QLabel(d->autoUpdaterStatusBarWidget);
803 d->autoUpdaterLabel->setText(tr(
"Auto Updater:"));
804 d->autoUpdaterStatusBarWidget->layout()->addWidget(d->autoUpdaterLabel);
806 d->autoUpdaterFileProgressBar = mkStdProgressBarForStatusBar();
807 d->autoUpdaterFileProgressBar->setFormat(
"%p%");
808 d->autoUpdaterFileProgressBar->setMaximumWidth(FILE_BAR_WIDTH);
809 d->autoUpdaterFileProgressBar->setMinimumWidth(FILE_BAR_WIDTH);
810 d->autoUpdaterStatusBarWidget->layout()->addWidget(d->autoUpdaterFileProgressBar);
812 d->autoUpdaterOverallProgressBar = mkStdProgressBarForStatusBar();
813 d->autoUpdaterOverallProgressBar->setMaximumWidth(OVERALL_BAR_WIDTH);
814 d->autoUpdaterOverallProgressBar->setMinimumWidth(OVERALL_BAR_WIDTH);
815 d->autoUpdaterStatusBarWidget->layout()->addWidget(d->autoUpdaterOverallProgressBar);
817 d->autoUpdaterAbortButton =
new QPushButton(statusBar());
818 d->autoUpdaterAbortButton->setToolTip(tr(
"Abort update."));
819 d->autoUpdaterAbortButton->setIcon(QIcon(
":/icons/x.png"));
820 this->connect(d->autoUpdaterAbortButton, SIGNAL(clicked()),
821 SLOT(abortAutoUpdater()));
822 d->autoUpdaterStatusBarWidget->layout()->addWidget(d->autoUpdaterAbortButton);
825 void MainWindow::initIP2CUpdater()
827 static const int PROGRESSBAR_WIDTH = 220;
829 d->ip2cUpdateProgressBar = mkStdProgressBarForStatusBar();
830 d->ip2cUpdateProgressBar->setFormat(tr(
"IP2C Update"));
831 d->ip2cUpdateProgressBar->hide();
832 d->ip2cUpdateProgressBar->setMaximumWidth(PROGRESSBAR_WIDTH);
833 d->ip2cUpdateProgressBar->setMinimumWidth(PROGRESSBAR_WIDTH);
834 statusBar()->addPermanentWidget(d->ip2cUpdateProgressBar);
837 void MainWindow::initIRCDock()
839 d->ircDock =
new IRCDock(
this);
840 d->menuView->addAction(d->ircDock->toggleViewAction());
841 d->ircDock->toggleViewAction()->setText(tr(
"&IRC"));
842 d->ircDock->toggleViewAction()->setShortcut(tr(
"CTRL+I"));
844 this->addDockWidget(Qt::BottomDockWidgetArea, d->ircDock);
848 this->d->ircDock->setVisible(
true);
849 this->d->ircDock->performNetworkAutojoins();
853 void MainWindow::initLogDock()
855 d->logDock =
new LogDock(
this);
856 d->menuView->addAction(d->logDock->toggleViewAction());
857 d->logDock->toggleViewAction()->setText(tr(
"&Log"));
858 d->logDock->toggleViewAction()->setShortcut(tr(
"CTRL+L"));
860 this->addDockWidget(Qt::BottomDockWidgetArea, d->logDock);
862 connect(&gLog, SIGNAL(newEntry(
const QString&)), d->logDock, SLOT(appendLogEntry(
const QString&)));
865 d->logDock->appendLogEntry(gLog.content());
868 void MainWindow::initMainDock()
870 setDockNestingEnabled(
true);
874 d->mainDock =
new QDockWidget(tr(
"Servers"));
875 d->mainDock->setTitleBarWidget(
new QWidget(
this));
876 d->mainDock->setObjectName(
"ServerList");
877 d->mainDock->setFeatures(QDockWidget::NoDockWidgetFeatures);
878 d->mainDock->setWidget(centralWidget());
879 setCentralWidget(
nullptr);
880 addDockWidget(Qt::RightDockWidgetArea, d->mainDock);
883 void MainWindow::initServerDetailsDock()
886 d->menuView->addAction(d->detailsDock->toggleViewAction());
887 d->detailsDock->toggleViewAction()->setText(tr(
"Server &details"));
888 d->detailsDock->toggleViewAction()->setShortcut(tr(
"CTRL+D"));
889 d->detailsDock->hide();
890 addDockWidget(Qt::BottomDockWidgetArea, d->detailsDock);
892 d->detailsDock->connect(d->serverList, SIGNAL(serversSelected(QList<ServerPtr>&)), SLOT(displaySelection(QList<ServerPtr> &)));
895 void MainWindow::initServerFilterDock()
899 d->menuView->addAction(d->serverFilterDock->toggleViewAction());
900 d->serverFilterDock->hide();
901 this->addDockWidget(Qt::RightDockWidgetArea, d->serverFilterDock);
906 bool isEnabled = gConfig.doomseeker.bUseTrayIcon;
907 if (!isEnabled || !QSystemTrayIcon::isSystemTrayAvailable())
909 if (d->trayIcon !=
nullptr)
912 d->trayIcon =
nullptr;
915 if (d->trayIconMenu !=
nullptr)
917 delete d->trayIconMenu;
918 d->trayIconMenu =
nullptr;
921 else if (d->trayIcon ==
nullptr)
924 d->trayIconMenu =
new QMenu(
this);
925 trayAction = d->trayIconMenu->addAction(
"Exit");
926 connect(trayAction, SIGNAL(triggered()),
this, SLOT(quitProgram()));
929 d->trayIcon =
new QSystemTrayIcon(
this);
930 connect(d->trayIcon, SIGNAL(activated(QSystemTrayIcon::ActivationReason)),
this, SLOT(trayIcon_activated(QSystemTrayIcon::ActivationReason)));
932 updateTrayIconTooltipAndLogTotalRefresh();
934 d->trayIcon->setContextMenu(d->trayIconMenu);
935 d->trayIcon->setIcon(QIcon(
":/icon.png"));
936 d->trayIcon->setVisible(
true);
940 void MainWindow::ip2cDownloadProgress(qint64 current, qint64 max)
942 d->ip2cUpdateProgressBar->setMaximum(max);
943 d->ip2cUpdateProgressBar->setValue(current);
946 void MainWindow::ip2cJobsFinished()
948 d->menuActionUpdateIP2C->setEnabled(
true);
949 d->serverList->updateCountryFlags();
950 d->ip2cUpdateProgressBar->hide();
952 if (d->ip2cLoader !=
nullptr)
954 delete d->ip2cLoader;
955 d->ip2cLoader =
nullptr;
959 void MainWindow::ip2cStartUpdate()
961 if (d->ip2cLoader !=
nullptr)
967 d->menuActionUpdateIP2C->setEnabled(
false);
968 d->ip2cUpdateProgressBar->show();
970 d->ip2cLoader =
new IP2CLoader(*IP2C::instance());
972 d->ip2cLoader->update();
977 return hasCustomServers() || isAnyMasterEnabled();
980 bool MainWindow::isAnyMasterEnabled()
const
982 for (
int i = 0; i < d->masterManager->numMasters(); ++i)
993 bool MainWindow::isEffectivelyActiveWindow()
const
995 return this->isActiveWindow() || DoomseekerConfigurationDialog::isOpen();
998 void MainWindow::masterManagerMessages(
MasterClient *pSender,
const QString &title,
const QString &content,
bool isError)
1001 QString message = tr(
"Master server for %1: %2").arg(pSender->
plugin()->data()->name).arg(content);
1005 message = tr(
"Error: %1").arg(message);
1006 statusBar()->showMessage(message);
1012 void MainWindow::masterManagerMessagesImportant(
MasterClient *pSender,
const Message &objMessage)
1014 QString strFullMessage = tr(
"%1: %2")
1015 .arg(pSender->
plugin()->data()->name)
1017 d->masterMessages << d->importantMessagesWidget->addMessage(strFullMessage, objMessage.
timestamp());
1020 void MainWindow::menuBuddies()
1022 d->buddiesList->setVisible(!d->buddiesList->isVisible());
1025 void MainWindow::menuCreateServer()
1029 dialog->setWindowIcon(this->windowIcon());
1033 void MainWindow::menuHelpAbout()
1036 d->autoRefreshTimer.stop();
1041 void MainWindow::menuHelpHelp()
1045 QMessageBox::critical(
this, tr(
"Help error"), tr(
"No help found."), QMessageBox::Ok, QMessageBox::Ok);
1049 bool bSuccess = QDesktopServices::openUrl(HELP_SITE_URL);
1053 QMessageBox::critical(
this, tr(
"Help error"), tr(
"Failed to open URL:\n%1").arg(HELP_SITE_URL), QMessageBox::Ok, QMessageBox::Ok);
1058 void MainWindow::menuIRCOptions()
1061 dialog.initOptionsList();
1064 if (d->ircDock !=
nullptr)
1066 d->ircDock->applyAppearanceSettings();
1071 d->ircDock->sounds().loadFromConfig();
1075 void MainWindow::menuLog()
1077 d->logDock->setVisible(!d->logDock->isVisible());
1080 void MainWindow::menuManageDemos()
1083 dm.setWindowIcon(this->windowIcon());
1087 void MainWindow::menuOptionsConfigure()
1089 DoomseekerConfigurationDialog::openConfiguration(
this);
1092 void MainWindow::menuRecordDemo()
1094 gConfig.doomseeker.bRecordDemo = d->menuActionRecordDemo->isChecked();
1097 void MainWindow::menuUpdateIP2C()
1101 connect(&updateBox, SIGNAL(accepted()),
this, SLOT(ip2cStartUpdate()));
1106 void MainWindow::menuViewIRC()
1108 d->ircDock->setVisible(!d->ircDock->isVisible());
1111 void MainWindow::menuWadSeeker()
1113 if (gWadseekerShow->checkWadseekerValidity(
this))
1116 wadseeker->setAttribute(Qt::WA_DeleteOnClose);
1121 QProgressBar *MainWindow::mkStdProgressBarForStatusBar()
1123 auto pBar =
new QProgressBar(statusBar());
1124 pBar->setAlignment(Qt::AlignCenter);
1125 pBar->setTextVisible(
true);
1126 pBar->setSizePolicy(QSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed));
1130 void MainWindow::notifyFirstRun()
1133 QMessageBox::information(
nullptr, tr(
"Welcome to Doomseeker"),
1134 tr(
"Before you start browsing for servers, please ensure that Doomseeker is properly configured."));
1135 d->menuActionConfigure->trigger();
1138 void MainWindow::onAutoUpdaterDownloadAndInstallConfirmationRequest()
1140 d->updatesConfirmationWidget->show();
1143 void MainWindow::onAutoUpdaterFileProgress(qint64 bytesReceived, qint64 bytesTotal)
1145 d->autoUpdaterFileProgressBar->setValue(bytesReceived);
1146 d->autoUpdaterFileProgressBar->setMaximum(bytesTotal);
1149 void MainWindow::onAutoUpdaterFinish()
1151 showAndLogStatusMessage(tr(
"Program update detection & download finished with status: [%1] %2")
1152 .arg((
int)d->autoUpdater->errorCode()).arg(d->autoUpdater->errorString()));
1153 gConfig.autoUpdates.bPerformUpdateOnNextRun =
false;
1154 if (d->autoUpdater->errorCode() == AutoUpdater::EC_Ok)
1157 if (channel == *d->updateChannelOnUpdateStart)
1159 if (!d->autoUpdater->downloadedPackagesFilenames().isEmpty())
1161 gLog << tr(
"Updates will be installed on next program start.");
1162 d->updatesDownloadedWidget->show();
1163 gConfig.autoUpdates.bPerformUpdateOnNextRun =
true;
1167 gLog << tr(
"Update channel was changed during update process. Discarding update.");
1169 gConfig.saveToFile();
1170 d->autoUpdaterStatusBarWidget->hide();
1171 d->updatesConfirmationWidget->hide();
1172 d->autoUpdater->deleteLater();
1173 d->autoUpdater =
nullptr;
1176 void MainWindow::onAutoUpdaterOverallProgress(
int current,
int total,
1179 d->autoUpdaterOverallProgressBar->setValue(current);
1180 d->autoUpdaterOverallProgressBar->setMaximum(total);
1181 d->autoUpdaterOverallProgressBar->setFormat(msg);
1184 void MainWindow::postInitAppStartup()
1190 if (gPlugins->numPlugins() > 0)
1192 bool bGettingServers =
false;
1193 bool queryOnStartup = gConfig.doomseeker.bQueryOnStartup;
1200 if (isAnyMasterEnabled())
1202 bGettingServers =
true;
1206 gLog << tr(
"Query on startup warning: No master servers are enabled in the Query menu.");
1211 if (!bGettingServers && hasCustomServers())
1216 refreshCustomServers();
1223 QString error = tr(
"Doomseeker was unable to find any plugin libraries.\n"
1224 "Although the application will still work it will not be possible "
1225 "to fetch any server info or launch any game.\n\n"
1226 "Please check if there are any files in \"engines/\" directory.\n"
1227 "To fix this problem you may try downloading Doomseeker "
1228 "again from the site specified in the Help|About box and reinstalling "
1230 QMessageBox::critical(
nullptr, tr(
"Doomseeker critical error"), error);
1234 QueryMenuAction *MainWindow::queryMenuActionForPlugin(
const EnginePlugin *plugin)
1236 if (plugin ==
nullptr)
1239 if (d->queryMenuPorts.contains(plugin))
1240 return d->queryMenuPorts[plugin];
1245 void MainWindow::quitProgram()
1247 d->bWantToQuit =
true;
1248 QApplication::closeAllWindows();
1251 void MainWindow::refreshCustomServers()
1253 for (
const ServerPtr &server : d->serverList->servers())
1255 if (server->isCustom())
1256 gRefresher->registerServer(server.data());
1260 void MainWindow::refreshServersOnList()
1262 for (
const ServerPtr &server : d->serverList->servers())
1264 gRefresher->registerServer(server.data());
1268 void MainWindow::refreshThreadBeginsWork()
1270 statusBar()->showMessage(tr(
"Querying..."));
1271 d->taskbarProgress->show();
1274 void MainWindow::refreshThreadEndsWork()
1276 d->toolBarGetServers->setEnabled(
true);
1278 d->serverList->cleanUpRightNow();
1279 statusBar()->showMessage(tr(
"Done"));
1280 updateTrayIconTooltipAndLogTotalRefresh();
1281 d->taskbarProgress->hide();
1283 if (d->bTotalRefreshInProcess)
1286 d->bTotalRefreshInProcess =
false;
1287 QList<ServerPtr> selectedServers = d->serverList->selectedServers();
1288 d->detailsDock->displaySelection(selectedServers);
1291 void MainWindow::restartAndInstallUpdatesNow()
1297 void MainWindow::runGame(
const ServerPtr &server)
1299 if (d->connectionHandler)
1300 delete d->connectionHandler;
1303 d->connectionHandler->run();
1308 assert(plugin !=
nullptr);
1310 QueryMenuAction *pAction = queryMenuActionForPlugin(plugin);
1311 if (pAction !=
nullptr)
1313 pAction->setChecked(bEnabled);
1314 if (plugin->data()->hasMasterClient())
1315 plugin->data()->masterClient->
setEnabled(bEnabled);
1316 if (plugin->data()->hasBroadcast())
1317 plugin->data()->broadcast->setEnabled(bEnabled);
1318 d->serversStatusesWidgets[plugin]->setMasterEnabledStatus(bEnabled);
1322 void MainWindow::serverAddedToList(
const ServerPtr &pServer)
1324 if (pServer->isKnown())
1326 const QString &gameMode = pServer->gameMode().
name();
1327 d->serverFilterDock->addGameModeToComboBox(gameMode);
1333 assert(this->d->updaterInstallerErrorCode == 0 &&
1334 "MainWindow::setDisplayUpdaterProcessFailure()");
1335 this->d->updaterInstallerErrorCode = errorCode;
1336 QTimer::singleShot(0,
this, SLOT(showUpdaterProcessErrorDialog()));
1341 assert(this->d->updaterInstallerErrorCode == 0 &&
1342 "MainWindow::setDisplayUpdateInstallerError()");
1343 this->d->updaterInstallerErrorCode = errorCode;
1344 QTimer::singleShot(0,
this, SLOT(showUpdateInstallErrorDialog()));
1347 void MainWindow::setupIcons()
1349 QStyle &style = *this->style();
1352 d->menuActionQuit->setIcon(style.standardIcon(QStyle::SP_TitleBarCloseButton));
1355 d->menuActionHelp->setIcon(style.standardIcon(QStyle::SP_MessageBoxQuestion));
1356 d->menuActionAbout->setIcon(style.standardIcon(QStyle::SP_MessageBoxInformation));
1359 void MainWindow::setupToolBar()
1361 QToolBar *pToolBar =
new QToolBar(tr(
"Main Toolbar"),
this);
1362 pToolBar->setMovable(
false);
1363 pToolBar->setObjectName(
"Toolbar");
1366 d->toolBarGetServers =
new QAction(QIcon(
":/icons/refresh.png"), tr(
"Get Servers"), pToolBar);
1370 pToolBar->addAction(d->toolBarGetServers);
1373 pToolBar->addSeparator();
1374 pToolBar->addAction(d->menuActionCreateServer);
1375 pToolBar->addAction(d->menuActionWadseeker);
1378 pToolBar->addSeparator();
1379 pToolBar->addAction(d->menuActionManageDemos);
1380 pToolBar->addAction(d->menuActionRecordDemo);
1382 pToolBar->addSeparator();
1385 pToolBar->addAction(d->buddiesList->toggleViewAction());
1386 pToolBar->addAction(d->logDock->toggleViewAction());
1387 pToolBar->addAction(d->ircDock->toggleViewAction());
1388 pToolBar->addAction(d->serverFilterDock->toggleViewAction());
1389 pToolBar->addAction(d->detailsDock->toggleViewAction());
1392 QLineEdit *qs = d->serverFilterDock->createQuickSearch();
1393 qs->setSizePolicy(QSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed));
1394 qs->setMinimumWidth(175);
1395 qs->setMaximumWidth(175);
1397 QWidget *searchSeparator =
new QWidget();
1398 searchSeparator->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::MinimumExpanding);
1399 pToolBar->addWidget(searchSeparator);
1400 pToolBar->addWidget(
new QLabel(tr(
"Search:"), pToolBar));
1401 pToolBar->addWidget(qs);
1403 this->addToolBar(Qt::TopToolBarArea, pToolBar);
1404 setUnifiedTitleAndToolBarOnMac(
true);
1405 connect(pToolBar, SIGNAL(actionTriggered(QAction*)),
this, SLOT(toolBarAction(QAction*)));
1408 void MainWindow::showAndLogStatusMessage(
const QString &message)
1411 statusBar()->showMessage(message);
1414 void MainWindow::showEvent(QShowEvent *event)
1418 d->taskbarButton->setWindow(windowHandle());
1421 void MainWindow::showInstallFreedoomDialog()
1423 if (!d->freedoomDialog.isNull())
1425 d->freedoomDialog->activateWindow();
1429 dialog->setAttribute(Qt::WA_DeleteOnClose);
1431 d->freedoomDialog = dialog;
1434 void MainWindow::showProgramArgsHelp()
1437 dialog->setAttribute(Qt::WA_DeleteOnClose);
1441 void MainWindow::showServerJoinCommandLine(
const ServerPtr &server)
1445 GameDemo demo = gConfig.doomseeker.bRecordDemo ? GameDemo::Unmanaged : GameDemo::NoDemo;
1447 this->connect(builder, SIGNAL(commandLineBuildFinished()), SLOT(showServerJoinCommandLineOnBuilderFinished()));
1451 void MainWindow::showServerJoinCommandLineOnBuilderFinished()
1457 QString execPath = cli.
executable.absoluteFilePath();
1458 QStringList args = cli.
args;
1463 CopyTextDlg ctd(execPath +
" " + args.join(
" "), builder->server()->name(),
this);
1468 if (!builder->error().isEmpty())
1470 GameConfigErrorBox::show(builder->server()->plugin(),
1471 tr(
"Doomseeker - show join command line"),
1472 tr(
"Failed to build the command line:\n%1").arg(builder->error()),
1476 builder->deleteLater();
1479 void MainWindow::showUpdaterProcessErrorDialog()
1481 QString explanation;
1482 if (this->d->updaterInstallerErrorCode != UpdateInstaller::PEC_GeneralFailure)
1484 QString errorCodeExplanation = UpdateInstaller::processErrorCodeToStr(
1486 explanation = tr(
"Update installation problem:\n%1").arg(errorCodeExplanation);
1489 explanation = tr(
"Update installation failed.");
1490 QMessageBox::critical(
this, tr(
"Doomseeker - Auto Update problem"),
1491 tr(
"%1\n\nRemaining updates have been discarded.").arg(explanation));
1494 void MainWindow::showUpdateInstallErrorDialog()
1496 QString error = UpdateInstaller::errorCodeToStr(
1498 QString msg = tr(
"Update install problem:\n%1\n\nRemaining updates have been discarded.").arg(error);
1499 QMessageBox::critical(
this, tr(
"Doomseeker - Auto Update problem"), msg);
1502 void MainWindow::stopAutoRefreshTimer()
1504 d->autoRefreshTimer.stop();
1509 QueryMenuAction *pAction = queryMenuActionForPlugin(plugin);
1510 assert(pAction !=
nullptr);
1515 void MainWindow::toolBarAction(QAction *pAction)
1517 if (pAction == d->toolBarGetServers)
1521 void MainWindow::trayIcon_activated(QSystemTrayIcon::ActivationReason reason)
1523 if (reason == QSystemTrayIcon::Trigger)
1525 if (isMinimized() || !isVisible())
1527 d->bWasMaximized ==
true ? showMaximized() : showNormal();
1530 else if (gConfig.doomseeker.bCloseToTrayIcon)
1537 void MainWindow::updateDynamicAppearance()
1539 d->tableServers->setShowGrid(gConfig.doomseeker.bDrawGridInServerTable);
1540 d->serverList->redraw();
1541 d->serverList->cleanUpForce();
1546 void MainWindow::updateMasterAddresses()
1548 for (
int i = 0; i < d->masterManager->numMasters(); i++)
1549 (*d->masterManager)[i]->updateAddress();
1554 d->serverList->applyFilter(filterInfo);
1563 count += status->count();
1568 void MainWindow::updateRefreshProgress()
1571 d->taskbarProgress->setMaximum(count.numServers);
1572 d->taskbarProgress->setValue(count.numServers - count.numRefreshing);
1573 updateTrayIconTooltip(count);
1578 if (d->trayIcon !=
nullptr)
1581 tip += tr(
"Generic servers: %1\n").arg(count.numGenericServers);
1582 tip += tr(
"Custom servers: %1\n").arg(count.numCustomServers);
1583 tip += tr(
"LAN servers: %1\n").arg(count.numLanServers);
1584 tip += tr(
"Human players: %1").arg(count.numHumanPlayers);
1585 d->trayIcon->setToolTip(tip);
1589 void MainWindow::updateTrayIconTooltipAndLogTotalRefresh()
1592 updateTrayIconTooltip(count);
1594 if (d->bTotalRefreshInProcess)
1596 gLog << tr(
"Finished refreshing. Servers on the list: %1 "
1597 "(+%2 custom, +%3 LAN). Players: %4.")
1598 .arg(count.numGenericServers).arg(count.numCustomServers)
1599 .arg(count.numLanServers).arg(count.numHumanPlayers);