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"];
600 setQueryPluginEnabled(plugin, enabled);
606 setQueryPluginEnabled(plugin,
true);
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->setCustomSite(server->webSite());
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()
706 if (!isAnythingToRefresh())
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();
1030 initAutoRefreshTimer();
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)
1279 initAutoRefreshTimer();
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);
1505 setQueryPluginEnabled(plugin, !pAction->isChecked());
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);
Manages the checking process of wads when trying to enter a server or when executing the "Find missin...
const QString & name() const
User-friendly name to display for game mode.
static void loadImages(const QString &style)
Structure describing server filter.
void checkForUpdatesAuto()
Auto triggered updates will display install confirmation only if configured to.
Performs a case-insensitive (OS independent) file searches.
Dockable widget designed for IRC communication.
Dockable widget designed for application's log presentation.
static UpdateChannel fromName(const QString &name)
Creates object from its internal name.
void togglePluginQueryEnabled(const EnginePlugin *plugin)
Structure holding parameters for application launch.
Message object used to pass messages throughout the Doomseeker's system.
void finishConfiguration(DoomseekerConfigurationDialog &, bool)
QStringList args
launch parameters
static bool bInstallUpdatesAndRestart
If true then program will install updates and restart instead of quitting if quit is requested...
Contains the results of CheckWadsDlg::CheckWads(), categorized in "incomplete", "missing" and "found"...
void setWads(const QList< PWad > &wads)
Sets WADs to seek.
bool isFilteringAnything() const
Informs if filter will apply to any server.
QVariant value() const
Extracts the value as QVariant.
Update is fully automatic.
ProcessErrorCode
"--update-failed" values.
static bool rmAllFiles(const QString &dirPath, const QStringList &nameFilters=QStringList())
Deletes all files in specified directory.
const QList< Checksum > checksums() const
List of checksums of the WAD.
void setQueryPluginEnabled(const EnginePlugin *pClient, bool bEnabled)
Sets query for selected MasterClient object to enabled or disabled.
Generates command line for joining specified server.
void checkForUpdatesUserTriggered()
User triggered updates will always display install confirmation.
IniVariable retrieveSetting(const QString &name)
Gets a variable but only if it already exists.
static bool isUrlSafe(const QString &url)
Unsafe URLs begin with file:// and this functions returns false for such URLs.
unsigned timestamp() const
Generation time in seconds since UTC epoch.
Dialog window allowing user to create a game.
void setDisplayUpdaterProcessFailure(int errorCode)
This should be set if program was started with "--update-failed" argument.
bool isAnythingToRefresh() const
Will check if refresh operation has any sense.
Platform-agnostic wrapper for QWinTaskbarProgress.
Deals with program updates/upgrades.
void obtainJoinCommandLine()
Runs asynchronously and emits commandLineBuildFinished() when done.
void fillQueryMenu(MasterManager *masterManager)
virtual const EnginePlugin * plugin() const =0
Wadseeker dialog box, only one instance is allowed.
Dialog for managing demos recorded through Doomseeker.
Manager class for a number of MasterClient instances.
INI section representation.
static void escapeExecutable(QString &arg)
Escapes the executable path and handles OS X bundles.
void setDisplayUpdateInstallerError(int errorCode)
If set then MainWindow will display the reason for updater failure.
QString contents() const
Customized displayable contents of this Message.
Abstract base for all MasterClients.
static void escapeArgs(QStringList &args)
Escapes all characters in all strings on the list.
QFileInfo executable
path to the executable
const QString & name() const
File name of the WAD.
bool isValid() const
It's valid when at least executable is set.
void initAutoRefreshTimer()