mainwindow.cpp
1 //------------------------------------------------------------------------------
2 // mainwindow.cpp
3 //------------------------------------------------------------------------------
4 //
5 // This library is free software; you can redistribute it and/or
6 // modify it under the terms of the GNU Lesser General Public
7 // License as published by the Free Software Foundation; either
8 // version 2.1 of the License, or (at your option) any later version.
9 //
10 // This library is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 // Lesser General Public License for more details.
14 //
15 // You should have received a copy of the GNU Lesser General Public
16 // License along with this library; if not, write to the Free Software
17 // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
18 // 02110-1301 USA
19 //
20 //------------------------------------------------------------------------------
21 // Copyright (C) 2009 "Zalewa" <zalewapl@gmail.com>
22 //------------------------------------------------------------------------------
23 #include "configuration/doomseekerconfig.h"
24 #include "configuration/queryspeed.h"
25 #include "gui/configuration/irc/ircconfigurationdialog.h"
26 #include "gui/configuration/doomseekerconfigurationdialog.h"
27 #include "gui/helpers/playersdiagram.h"
28 #include "gui/irc/ircdock.h"
29 #include "gui/irc/ircsounds.h"
30 #include "gui/models/serverlistmodel.h"
31 #include "gui/widgets/serversstatuswidget.h"
32 #include "gui/aboutdialog.h"
33 #include "gui/copytextdlg.h"
34 #include "gui/createserverdialog.h"
35 #include "gui/demomanager.h"
36 #include "gui/dockBuddiesList.h"
37 #include "gui/helpers/taskbarbutton.h"
38 #include "gui/helpers/taskbarprogress.h"
39 #include "gui/freedoomdialog.h"
40 #include "gui/ip2cupdatebox.h"
41 #include "gui/logdock.h"
42 #include "gui/mainwindow.h"
43 #include "gui/programargshelpdialog.h"
44 #include "gui/serverdetailsdock.h"
45 #include "gui/serverfilterdock.h"
46 #include "gui/serverlist.h"
47 #include "gui/wadseekerinterface.h"
48 #include "gui/wadseekershow.h"
49 #include "ip2c/ip2cloader.h"
50 #include "irc/configuration/chatnetworkscfg.h"
51 #include "irc/configuration/ircconfig.h"
52 #include "pathfinder/pathfinder.h"
53 #include "plugins/engineplugin.h"
54 #include "plugins/pluginloader.h"
55 #include "refresher/refresher.h"
56 #include "serverapi/broadcast.h"
57 #include "serverapi/broadcastmanager.h"
58 #include "serverapi/gameclientrunner.h"
59 #include "serverapi/mastermanager.h"
60 #include "serverapi/message.h"
61 #include "serverapi/server.h"
62 #include "serverapi/serverlistcounttracker.h"
63 #include "updater/autoupdater.h"
64 #include "updater/updatechannel.h"
65 #include "updater/updateinstaller.h"
66 #include "updater/updatepackage.h"
67 #include "application.h"
68 #include "apprunner.h"
69 #include "commandline.h"
70 #include "connectionhandler.h"
71 #include "customservers.h"
72 #include "datapaths.h"
73 #include "doomseekerfilepaths.h"
74 #include "fileutils.h"
75 #include "gamedemo.h"
76 #include "joincommandlinebuilder.h"
77 #include "pathfinder/wadpathfinder.h"
78 #include "log.h"
79 #include "main.h"
80 #include "strings.hpp"
81 #include "ui_mainwindow.h"
82 #include <cassert>
83 #include <QAction>
84 #include <QApplication>
85 #include <QDesktopServices>
86 #include <QDesktopWidget>
87 #include <QDockWidget>
88 #include <QFileInfo>
89 #include <QHeaderView>
90 #include <QIcon>
91 #include <QLineEdit>
92 #include <QMessageBox>
93 #include <QPointer>
94 #include <QProgressBar>
95 #include <QToolBar>
96 #include <QSizePolicy>
97 #include <QMessageBox>
98 
99 const QString MainWindow::HELP_SITE_URL = "https://doomseeker.drdteam.org/help";
100 
108 class QueryMenuAction : public QAction
109 {
110  public:
111  QueryMenuAction(const EnginePlugin *plugin, ServersStatusWidget *statusWidget, QObject* parent = NULL)
112  :QAction(parent)
113  {
114  this->pPlugin = plugin;
115 
116  if (plugin != NULL)
117  {
118  connect(this, SIGNAL( toggled(bool) ), plugin->data()->masterClient,
119  SLOT( setEnabled(bool) ) );
120  connect(this, SIGNAL( toggled(bool) ), statusWidget, SLOT( setMasterEnabledStatus(bool) ) );
121  }
122  }
123 
124  const EnginePlugin* plugin() const
125  {
126  return pPlugin;
127  }
128 
129  private:
130  const EnginePlugin* pPlugin;
131 };
132 
133 DClass<MainWindow> : public Ui::MainWindowWnd
134 {
135 public:
136  PrivData() : bTotalRefreshInProcess(false), buddiesList(NULL),
137  bWasMaximized(false), bWantToQuit(false), logDock(NULL),
138  masterManager(NULL), trayIcon(NULL), trayIconMenu(NULL)
139  {
140  }
141 
142  QApplication* application;
143  QTimer autoRefreshTimer;
144 
145  AutoUpdater* autoUpdater;
146  QWidget* autoUpdaterStatusBarWidget;
147  QPushButton* autoUpdaterAbortButton;
148  QLabel* autoUpdaterLabel;
149  QProgressBar* autoUpdaterFileProgressBar;
150  QProgressBar* autoUpdaterOverallProgressBar;
151 
156  bool bTotalRefreshInProcess;
157 
158  DockBuddiesList* buddiesList;
159  BroadcastManager *broadcastManager;
160 
164  bool bWasMaximized;
165 
171  bool bWantToQuit;
172 
173  IP2CLoader* ip2cLoader;
174  QProgressBar* ip2cUpdateProgressBar;
175  IRCDock* ircDock;
176  LogDock* logDock;
177  ServerDetailsDock* detailsDock;
178  QPointer<FreedoomDialog> freedoomDialog;
179  ServerFilterDock* serverFilterDock;
180  ServerList* serverList;
181 
182  MasterManager* masterManager;
183  QHash<const EnginePlugin*, QueryMenuAction*> queryMenuPorts;
184  QHash<const EnginePlugin*, ServersStatusWidget*> serversStatusesWidgets;
185  QAction* toolBarGetServers;
186  QSystemTrayIcon* trayIcon;
187  QMenu* trayIconMenu;
189  UpdateChannel* updateChannelOnUpdateStart;
190  int updaterInstallerErrorCode;
191 
192  ConnectionHandler *connectionHandler;
193  QDockWidget *mainDock;
194 
195  TaskbarProgress *taskbarProgress;
196  TaskbarButton *taskbarButton;
197 };
198 
199 DPointeredNoCopy(MainWindow)
200 
201 MainWindow::MainWindow(QApplication* application, int argc, char** argv)
202 {
203  d->autoUpdater = NULL;
204  d->mainDock = NULL;
205  d->connectionHandler = NULL;
206  d->updateChannelOnUpdateStart = new UpdateChannel();
207  d->updaterInstallerErrorCode = 0;
208 
209  d->application = application;
210 
211  this->setAttribute(Qt::WA_DeleteOnClose, true);
212  d->setupUi(this);
213 
214  d->taskbarButton = new TaskbarButton(this);
215  d->taskbarProgress = d->taskbarButton->progress();
216 
217  setupIcons();
218 
219  initAutoUpdaterWidgets();
220 
221  d->updatesConfirmationWidget->hide();
222  d->updatesDownloadedWidget->hide();
223 
224  // Hide menu options which aren't supported on target platform.
225  #ifndef WITH_AUTOUPDATES
226  d->menuActionCheckForUpdates->setVisible(false);
227  #endif
228 
229  if (gPlugins->numPlugins() == 0)
230  {
231  QString message = MainWindow::tr(
232  "Warning: \n"
233  "Doomseeker failed to detect any plugins.\n"
234  "While the core application will still work its functionality is going to be limited.\n"
235  "One of the proper locations for plugin modules is the \"engines/\" directory.\n"
236  );
237  gLog << message;
238  QMessageBox::critical(NULL, MainWindow::tr("Doomseeker - plugin load failure"), message);
239  }
240 
241  initIP2CUpdater();
242 
243  // The buddies list must always be available so we can perform certain operations on it
244  d->buddiesList = new DockBuddiesList(this);
245  d->menuView->addAction(d->buddiesList->toggleViewAction());
246  d->buddiesList->toggleViewAction()->setText(MainWindow::tr("&Buddies"));
247  d->buddiesList->toggleViewAction()->setShortcut(MainWindow::tr("CTRL+B"));
248 
249  connect(d->buddiesList, SIGNAL(joinServer(ServerPtr)), this, SLOT(runGame(ServerPtr)));
250  d->buddiesList->hide();
251  this->addDockWidget(Qt::LeftDockWidgetArea, d->buddiesList);
252  initLogDock();
253  initIRCDock();
254  initServerFilterDock();
255  initMainDock();
256  splitDockWidget(d->mainDock, d->serverFilterDock, Qt::Horizontal);
257 
258  // Spawn Server Table Handler.
259  d->serverList = new ServerList(d->tableServers, this);
260  connectEntities();
261 
262  d->broadcastManager = new BroadcastManager(this);
263  d->serverList->connect(d->broadcastManager,
264  SIGNAL(newServerDetected(ServerPtr, int)), SLOT(registerServer(ServerPtr)));
265  d->serverList->connect(d->broadcastManager,
266  SIGNAL(serverLost(ServerPtr)), SLOT(removeServer(ServerPtr)));
267 
268  initServerDetailsDock();
269  tabifyDockWidget(d->ircDock, d->detailsDock);
270 
271  // Restore checked states.
272  d->menuActionRecordDemo->setChecked(gConfig.doomseeker.bRecordDemo);
273 
274  // Get the master
275  d->masterManager = new MasterManager();
276  d->buddiesList->scan(d->masterManager);
277  connect(d->masterManager, SIGNAL( masterMessage(MasterClient*, const QString&, const QString&, bool) ),
278  this, SLOT( masterManagerMessages(MasterClient*, const QString&, const QString&, bool) ) );
279  connect(d->masterManager, SIGNAL( masterMessageImportant(MasterClient*, const Message&) ),
280  this, SLOT( masterManagerMessagesImportant(MasterClient*, const Message&) ));
281 
282  // Allow us to enable and disable ports.
283  fillQueryMenu(d->masterManager);
284 
285  // Init custom servers
286  QList<ServerPtr> customServers = d->masterManager->customServs()->readConfig();
287  foreach (ServerPtr server, customServers)
288  {
289  d->serverList->registerServer(server);
290  }
291 
292  setWindowIcon(Application::icon());
293 
294  // Auto refresh timer
295  initAutoRefreshTimer();
296  connect(&d->autoRefreshTimer, SIGNAL( timeout() ), this, SLOT( autoRefreshTimer_timeout() ));
297 
298  // Tray icon
299  initTrayIcon();
300 
301  setupToolBar();
302 
303  // Player diagram styles
304  PlayersDiagram::loadImages(gConfig.doomseeker.slotStyle);
305 
306  // IP2C
307  d->menuActionUpdateIP2C->setEnabled(false);
308  d->ip2cLoader = new IP2CLoader();
309  connectIP2CLoader(d->ip2cLoader);
310  d->ip2cLoader->load();
311 
312  restoreState(QByteArray::fromBase64(gConfig.doomseeker.mainWindowState.toUtf8()));
313  restoreGeometry(gConfig.doomseeker.mainWindowGeometry);
314 
315  // Start first refresh from a timer. We want the main window fully
316  // set up before refresh.
317  QTimer::singleShot(1, this, SLOT(postInitAppStartup()));
318 }
319 
320 MainWindow::~MainWindow()
321 {
322  // Window geometry settings
323  gConfig.doomseeker.mainWindowGeometry = saveGeometry();
324  gConfig.doomseeker.mainWindowState = saveState().toBase64();
325 
326  if (d->updateChannelOnUpdateStart != NULL)
327  {
328  delete d->updateChannelOnUpdateStart;
329  }
330  if (d->autoUpdater != NULL)
331  {
332  d->autoUpdater->disconnect();
333  delete d->autoUpdater;
334  }
335  if(d->connectionHandler)
336  delete d->connectionHandler;
337 
338  QList<QAction*> menuQueryActions = d->menuQuery->actions();
339  QList<QAction*>::iterator it;
340  foreach (QAction *action, menuQueryActions)
341  {
342  QString pluginName = action->text();
343 
344  if (!pluginName.isEmpty())
345  {
346  IniSection pluginConfig = gConfig.iniSectionForPlugin(pluginName);
347  pluginConfig["Query"] = action->isChecked();
348  }
349  }
350 
351  if (d->trayIcon != NULL)
352  {
353  d->trayIcon->setVisible(false);
354  delete d->trayIcon;
355  d->trayIcon = NULL;
356  }
357 
358  if (d->trayIconMenu != NULL)
359  {
360  delete d->trayIconMenu;
361  d->trayIconMenu = NULL;
362  }
363 
364  delete d->serverList;
365 
366  if(d->masterManager != NULL)
367  {
368  delete d->masterManager;
369  }
370 
371  if (d->ip2cLoader != NULL)
372  {
373  delete d->ip2cLoader;
374  }
375 }
376 
377 void MainWindow::abortAutoUpdater()
378 {
379  if (d->autoUpdater != NULL)
380  {
381  d->autoUpdater->abort();
382  }
383 }
384 
385 void MainWindow::autoRefreshTimer_timeout()
386 {
387  if (gConfig.doomseeker.bQueryAutoRefreshDontIfActive && !isMinimized())
388  {
389  if (QApplication::activeWindow() != 0)
390  {
391  return;
392  }
393  }
394 
395  getServers();
396 }
397 
398 void MainWindow::blockRefreshButtons()
399 {
400  d->toolBarGetServers->setEnabled(false);
401 }
402 
403 DockBuddiesList *MainWindow::buddiesList()
404 {
405  return d->buddiesList;
406 }
407 
408 void MainWindow::changeEvent(QEvent* event)
409 {
410  if (event->type() == QEvent::ActivationChange && isActiveWindow() && !isMinimized() && !isHidden())
411  {
412  d->serverList->cleanUp();
413  event->accept();
414  }
415  QMainWindow::changeEvent(event);
416 }
417 
418 void MainWindow::checkForUpdates(bool bUserTriggered)
419 {
420  if (d->autoUpdater != NULL)
421  {
422  if (d->autoUpdater->isRunning())
423  {
424  QMessageBox::warning(this, tr("Doomseeker - Auto Update"),
425  tr("Update is already in progress."));
426  return;
427  }
428  else
429  {
430  delete d->autoUpdater;
431  d->autoUpdater = NULL;
432  }
433  }
434  gLog << tr("Removing old update packages from local temporary space.");
435  QStringList removeFilter(QString("%1*").arg(DataPaths::UPDATE_PACKAGE_FILENAME_PREFIX));
436  FileUtils::rmAllFiles(DoomseekerFilePaths::updatePackagesStorageDir(),
437  removeFilter);
438 
439  showAndLogStatusMessage(tr("Checking for updates..."));
440  d->autoUpdater = new AutoUpdater();
441  this->connect(d->autoUpdater, SIGNAL(statusMessage(QString)),
442  SLOT(showAndLogStatusMessage(QString)));
443  this->connect(d->autoUpdater, SIGNAL(finished()),
444  SLOT(onAutoUpdaterFinish()));
445  this->connect(d->autoUpdater, SIGNAL(downloadAndInstallConfirmationRequested()),
446  SLOT(onAutoUpdaterDownloadAndInstallConfirmationRequest()));
447  this->connect(d->autoUpdater, SIGNAL(overallProgress(int, int, const QString&)),
448  SLOT(onAutoUpdaterOverallProgress(int, int, const QString&)));
449  this->connect(d->autoUpdater, SIGNAL(packageDownloadProgress(qint64, qint64)),
450  SLOT(onAutoUpdaterFileProgress(qint64, qint64)));
451 
452  QMap<QString, QList<QString> > ignoredPackagesRevisions;
453  if (!bUserTriggered)
454  {
455  foreach (const QString& package, gConfig.autoUpdates.lastKnownUpdateRevisions.keys())
456  {
457  QString revision = gConfig.autoUpdates.lastKnownUpdateRevisions[package];
458  QList<QString> list;
459  list << revision;
460  ignoredPackagesRevisions.insert(package, list);
461  }
462  }
463  d->autoUpdater->setIgnoreRevisions(ignoredPackagesRevisions);
464 
465  UpdateChannel channel = UpdateChannel::fromName(gConfig.autoUpdates.updateChannelName);
466  d->autoUpdater->setChannel(channel);
467  *d->updateChannelOnUpdateStart = channel;
468 
469  bool bRequireConfirmation = true;
470  if (!bUserTriggered)
471  {
472  bRequireConfirmation = (gConfig.autoUpdates.updateMode
474  }
475  d->autoUpdater->setRequireDownloadAndInstallConfirmation(bRequireConfirmation);
476  d->autoUpdaterStatusBarWidget->show();
477  d->autoUpdater->start();
478 }
479 
480 
482 {
483  const bool bUserTriggered = true;
484  checkForUpdates(!bUserTriggered);
485 }
486 
488 {
489  const bool bUserTriggered = true;
490  checkForUpdates(bUserTriggered);
491 }
492 
493 void MainWindow::closeEvent(QCloseEvent* event)
494 {
495  // Check if tray icon is available and if we want to minimize to tray icon
496  // when 'X' button is pressed. Real quit requests are handled by
497  // quitProgram() method. This method sets d->bWantToQuit to true.
498  if (d->trayIcon != NULL && gConfig.doomseeker.bCloseToTrayIcon && !d->bWantToQuit)
499  {
500  d->bWasMaximized = isMaximized();
501  event->ignore();
502  hide();
503  }
504  else
505  {
506  event->accept();
507  }
508 }
509 
510 void MainWindow::confirmUpdateInstallation()
511 {
512  assert(d->autoUpdater != NULL && "MainWindow::confirmUpdateInstallation()");
513  d->updatesConfirmationWidget->hide();
514  d->autoUpdater->confirmDownloadAndInstall();
515 }
516 
517 void MainWindow::connectIP2CLoader(IP2CLoader* loader)
518 {
519  this->connect(d->ip2cLoader, SIGNAL( finished() ), SLOT( ip2cJobsFinished()));
520  this->connect(d->ip2cLoader, SIGNAL( downloadProgress(qint64, qint64) ),
521  SLOT( ip2cDownloadProgress(qint64, qint64) ) );
522 }
523 
524 void MainWindow::discardUpdates()
525 {
526  assert(d->autoUpdater != NULL && "MainWindow::confirmUpdateInstallation()");
527  d->updatesConfirmationWidget->hide();
528  // User rejected this update so let's add the packages
529  // to the ignore list so user won't be nagged again.
530  const QList<UpdatePackage>& pkgList = d->autoUpdater->newUpdatePackages();
531  foreach (const UpdatePackage& pkg, pkgList)
532  {
533  gConfig.autoUpdates.lastKnownUpdateRevisions.insert(pkg.name, pkg.revision);
534  }
535  d->autoUpdater->abort();
536 }
537 
539 {
540  // Connect refreshing thread.
541  connect(gRefresher, SIGNAL( block() ), this, SLOT( blockRefreshButtons() ) );
542  connect(gRefresher, SIGNAL( finishedQueryingMaster(MasterClient*) ), this, SLOT( finishedQueryingMaster(MasterClient*) ) );
543  connect(gRefresher, SIGNAL( sleepingModeEnter() ), this, SLOT( refreshThreadEndsWork() ) );
544  connect(gRefresher, SIGNAL( sleepingModeEnter() ), d->buddiesList, SLOT( scan() ) );
545  connect(gRefresher, SIGNAL( sleepingModeExit() ), this, SLOT( refreshThreadBeginsWork() ) );
546 
547  // Controls
548  connect(d->menuActionAbout, SIGNAL( triggered() ), this, SLOT( menuHelpAbout() ));
549  connect(d->menuActionAboutQt, SIGNAL( triggered() ), d->application, SLOT( aboutQt() ));
550  connect(d->menuActionBuddies, SIGNAL( triggered() ), this, SLOT( menuBuddies() ));
551  connect(d->menuActionConfigure, SIGNAL( triggered() ), this, SLOT( menuOptionsConfigure() ));
552  connect(d->menuActionCreateServer, SIGNAL( triggered() ), this, SLOT( menuCreateServer() ));
553  connect(d->menuActionHelp, SIGNAL( triggered() ), this, SLOT ( menuHelpHelp() ) );
554  connect(d->menuActionIRCOptions, SIGNAL( triggered() ), this, SLOT( menuIRCOptions() ) );
555  connect(d->menuActionLog, SIGNAL( triggered() ), this, SLOT( menuLog() ));
556  connect(d->menuActionManageDemos, SIGNAL( triggered() ), this, SLOT( menuManageDemos() ) );
557  connect(d->menuActionRecordDemo, SIGNAL( triggered() ), this, SLOT( menuRecordDemo() ) );
558  connect(d->menuActionUpdateIP2C, SIGNAL( triggered() ), this, SLOT( menuUpdateIP2C() ) );
559  connect(d->menuActionQuit, SIGNAL( triggered() ), this, SLOT( quitProgram() ));
560  connect(d->menuActionViewIRC, SIGNAL( triggered() ) , this, SLOT( menuViewIRC() ));
561  connect(d->menuActionWadseeker, SIGNAL( triggered() ), this, SLOT( menuWadSeeker() ));
562  connect(d->serverFilterDock, SIGNAL(filterUpdated(const ServerListFilterInfo&)),
563  this, SLOT(updateServerFilter(const ServerListFilterInfo&)) );
564  connect(d->serverFilterDock, SIGNAL(nonEmptyServerGroupingAtTopToggled(bool)),
565  d->serverList, SLOT(setGroupServersWithPlayersAtTop(bool)) );
566  connect(d->serverList, SIGNAL(serverFilterModified(ServerListFilterInfo)),
567  d->serverFilterDock, SLOT(setFilterInfo(ServerListFilterInfo)));
568  connect(d->serverList, SIGNAL( serverDoubleClicked(ServerPtr) ), this, SLOT( runGame(ServerPtr) ) );
569  connect(d->serverList, SIGNAL( displayServerJoinCommandLine(const ServerPtr&) ), this, SLOT( showServerJoinCommandLine(const ServerPtr&) ) );
570  connect(d->serverList, SIGNAL( findMissingWADs(const ServerPtr&) ), this, SLOT( findMissingWADs(const ServerPtr&) ) );
571  connect(d->serverList, SIGNAL( serverInfoUpdated(ServerPtr) ), this, SLOT( serverAddedToList(ServerPtr) ) );
572  connect(d->buddiesList, SIGNAL(scanCompleted()), d->serverList, SLOT(redraw()));
573 }
574 
576 {
577  // This is called only once from the constructor. No clears to
578  // d->queryMenuPorts are ever performed. Not even in the destructor.
579  for(unsigned i = 0; i < gPlugins->numPlugins(); ++i)
580  {
581  const EnginePlugin* plugin = gPlugins->info(i);
582  if(!plugin->data()->hasMasterClient() && !plugin->data()->hasBroadcast())
583  {
584  continue;
585  }
586 
587  if (plugin->data()->hasMasterClient())
588  {
589  MasterClient* pMasterClient = plugin->data()->masterClient;
590  pMasterClient->updateAddress();
591  masterManager->addMaster(pMasterClient);
592  }
593 
594  if (plugin->data()->hasBroadcast())
595  {
596  d->broadcastManager->registerPlugin(plugin);
597  }
598 
599  // Now is a good time to also populate the status bar widgets
600  ServersStatusWidget *statusWidget = new ServersStatusWidget(plugin, d->serverList);
601  d->serversStatusesWidgets.insert(plugin, statusWidget);
602 
603  this->connect(statusWidget, SIGNAL( clicked(const EnginePlugin*) ) ,
604  SLOT( togglePluginQueryEnabled(const EnginePlugin*) ) );
605  this->connect(statusWidget, SIGNAL(counterUpdated()),
606  SLOT(updateRefreshProgress()));
607 
608  statusBar()->addPermanentWidget(statusWidget);
609 
610  QString name = gPlugins->info(i)->data()->name;
611  QueryMenuAction* query = new QueryMenuAction(plugin, statusWidget, d->menuQuery);
612  d->queryMenuPorts.insert(plugin, query);
613 
614  d->menuQuery->addAction(query);
615 
616  query->setCheckable(true);
617  query->setIcon(plugin->icon());
618  query->setText(name);
619 
620  IniSection pluginConfig = gConfig.iniSectionForPlugin(name);
621 
622  if (!pluginConfig.retrieveSetting("Query").value().isNull())
623  {
624  bool enabled = pluginConfig["Query"];
625  setQueryPluginEnabled(plugin, enabled);
626  }
627  else
628  {
629  // if no setting is found for this engine
630  // set default to true:
631  setQueryPluginEnabled(plugin, true);
632  }
633  }
634 }
635 
636 void MainWindow::findMissingWADs(const ServerPtr &server)
637 {
638  // Display a message if all WADs are present.
639  QList<PWad> wads = server->wads();
640  wads << server->iwad();
641  PathFinder pathFinder = server->wadPathFinder();
642  WadPathFinder wadFinder(pathFinder);
643  QList<PWad> missingWads;
644  foreach(const PWad &wad, wads)
645  {
646  if(!wadFinder.find(wad.name()).isValid())
647  {
648  PWad optionalWad = PWad(wad.name(), true);
649  missingWads << optionalWad;
650  }
651  }
652  if (missingWads.isEmpty())
653  {
654  QMessageBox::information(this, tr("All WADs found"), tr("All of the WADs used by this server are present."));
655  return;
656  }
657 
658  MissingWadsDialog dialog(missingWads, this);
659  dialog.setAllowIgnore(false);
660  if (dialog.exec() == QDialog::Accepted && dialog.decision() == MissingWadsDialog::Install)
661  {
662  if (!gWadseekerShow->checkWadseekerValidity(this))
663  {
664  return;
665  }
666  WadseekerInterface *wadseeker = WadseekerInterface::createAutoNoGame();
667  wadseeker->setCustomSite(server->webSite());
668  wadseeker->setWads(dialog.filesToDownload());
669  wadseeker->setAttribute(Qt::WA_DeleteOnClose);
670  wadseeker->show();
671  }
672 }
673 
674 void MainWindow::finishConfiguration(DoomseekerConfigurationDialog &configDialog, bool lookupHostsChanged)
675 {
676  // In case the master server addresses changed, notify the master clients.
677  updateMasterAddresses();
678  gRefresher->setDelayBetweenResends(gConfig.doomseeker.querySpeed().delayBetweenSingleServerAttempts);
679 
680  // If appearance changed - update the widgets.
681  if (configDialog.wasAppearanceChanged())
682  {
683  updateDynamicAppearance();
684  initTrayIcon();
685  }
686 
687  // If changes require a restart, tell the user
688  if (configDialog.isRestartNeeded())
689  {
690  QString warningRestartNeeded = tr("Doomseeker needs to be restarted for some changes to be applied.");
691  d->importantMessagesWidget->addMessage(warningRestartNeeded);
692  }
693 
694  // Do the following only if setting changed from false to true.
695  if (lookupHostsChanged)
696  {
697  d->serverList->lookupHosts();
698  }
699 
700  // Refresh custom servers list:
701  if (configDialog.customServersChanged())
702  {
703  d->serverList->removeCustomServers();
704  QList<ServerPtr> servers = d->masterManager->customServs()->readConfig();
705  foreach (ServerPtr server, servers)
706  {
707  d->serverList->registerServer(server);
708  }
709  refreshCustomServers();
710  }
711 }
712 
713 void MainWindow::finishedQueryingMaster(MasterClient* master)
714 {
715  if (master == NULL)
716  {
717  return;
718  }
719 
720  for(int i = 0;i < master->numServers();i++)
721  {
722  d->serverList->registerServer((*master)[i]);
723  }
724 }
725 
726 void MainWindow::fixIconsDpi()
727 {
728  // http://blog.qt.io/blog/2013/04/25/retina-display-support-for-mac-os-ios-and-x11/
729  QIcon icon(":/icons/exclamation_16.png");
730  d->lblExclamation1->setPixmap(icon.pixmap(16));
731  d->lblExclamation2->setPixmap(icon.pixmap(16));
732 }
733 
734 void MainWindow::getServers()
735 {
736  // Check if this operation has any sense.
737  if (!isAnythingToRefresh())
738  {
739  QString message = tr("Doomseeker is unable to proceed with the refresh"
740  " operation because the following problem has occured:\n\n");
741 
742  if (gPlugins->numPlugins() == 0)
743  {
744  message += tr("Plugins are missing from the \"engines/\" directory.");
745  }
746  else if (!isAnyMasterEnabled())
747  {
748  message += tr("No master servers are enabled in the \"Query\" menu.");
749  }
750  else
751  {
752  message += tr("Unknown error occured.");
753  }
754 
755  gLog << message;
756  QMessageBox::warning(this, tr("Doomseeker - refresh problem"), message);
757  return;
758  }
759 
760  d->bTotalRefreshInProcess = true;
761  d->autoRefreshTimer.stop();
762  gLog << tr("Total refresh initialized!");
763 
764  // Remove all non special servers. Master servers will
765  // "re-provide" them if they are still alive. Whatever
766  // remains on the list should be refreshed now.
767  d->serverList->removeNonSpecialServers();
768  refreshServersOnList();
769 
770  if (!isAnyMasterEnabled() && !d->serverList->hasAtLeastOneServer())
771  {
772  gLog << tr("Warning: No master servers were enabled for this refresh. "
773  "Check your Query menu or \"engines/\" directory.");
774  }
775 
776  d->masterManager->clearServers();
777  for (int i = 0; i < d->masterManager->numMasters(); ++i)
778  {
779  MasterClient* pMaster = (*d->masterManager)[i];
780 
781  if (pMaster->isEnabled())
782  {
783  gRefresher->registerMaster(pMaster);
784  }
785  }
786 }
787 
788 bool MainWindow::hasCustomServers() const
789 {
790  CustomServers* customServers = d->masterManager->customServs();
791  return customServers->numServers() > 0;
792 }
793 
795 {
796  const unsigned MIN_DELAY = 30;
797  const unsigned MAX_DELAY = 3600;
798 
799  bool bEnabled = gConfig.doomseeker.bQueryAutoRefreshEnabled;
800 
801  if (!bEnabled)
802  {
803  d->autoRefreshTimer.stop();
804  }
805  else
806  {
807  // If delay value is out of bounds we should adjust
808  // config value as well.
809  unsigned& delay = gConfig.doomseeker.queryAutoRefreshEverySeconds;
810 
811  // Make sure delay is in given limit.
812  if (delay < MIN_DELAY)
813  {
814  delay = MIN_DELAY;
815  }
816  else if (delay > MAX_DELAY)
817  {
818  delay = MAX_DELAY;
819  }
820 
821  unsigned delayMs = delay * 1000;
822 
823  d->autoRefreshTimer.setSingleShot(false);
824  d->autoRefreshTimer.start(delayMs);
825  }
826 }
827 
828 void MainWindow::initAutoUpdaterWidgets()
829 {
830  static const int FILE_BAR_WIDTH = 50;
831  static const int OVERALL_BAR_WIDTH = 180;
832 
833  d->autoUpdaterStatusBarWidget = new QWidget(statusBar());
834  d->autoUpdaterStatusBarWidget->setLayout(new QHBoxLayout(d->autoUpdaterStatusBarWidget));
835  d->autoUpdaterStatusBarWidget->layout()->setContentsMargins(QMargins(0, 0, 0, 0));
836  statusBar()->addPermanentWidget(d->autoUpdaterStatusBarWidget);
837  d->autoUpdaterStatusBarWidget->hide();
838 
839  d->autoUpdaterLabel = new QLabel(d->autoUpdaterStatusBarWidget);
840  d->autoUpdaterLabel->setText(tr("Auto Updater:"));
841  d->autoUpdaterStatusBarWidget->layout()->addWidget(d->autoUpdaterLabel);
842 
843  d->autoUpdaterFileProgressBar = mkStdProgressBarForStatusBar();
844  d->autoUpdaterFileProgressBar->setFormat("%p%");
845  d->autoUpdaterFileProgressBar->setMaximumWidth(FILE_BAR_WIDTH);
846  d->autoUpdaterFileProgressBar->setMinimumWidth(FILE_BAR_WIDTH);
847  d->autoUpdaterStatusBarWidget->layout()->addWidget(d->autoUpdaterFileProgressBar);
848 
849  d->autoUpdaterOverallProgressBar = mkStdProgressBarForStatusBar();
850  d->autoUpdaterOverallProgressBar->setMaximumWidth(OVERALL_BAR_WIDTH);
851  d->autoUpdaterOverallProgressBar->setMinimumWidth(OVERALL_BAR_WIDTH);
852  d->autoUpdaterStatusBarWidget->layout()->addWidget(d->autoUpdaterOverallProgressBar);
853 
854  d->autoUpdaterAbortButton = new QPushButton(statusBar());
855  d->autoUpdaterAbortButton->setToolTip(tr("Abort update."));
856  d->autoUpdaterAbortButton->setIcon(QIcon(":/icons/x.png"));
857  this->connect(d->autoUpdaterAbortButton, SIGNAL(clicked()),
858  SLOT(abortAutoUpdater()));
859  d->autoUpdaterStatusBarWidget->layout()->addWidget(d->autoUpdaterAbortButton);
860 }
861 
862 void MainWindow::initIP2CUpdater()
863 {
864  static const int PROGRESSBAR_WIDTH = 220;
865 
866  d->ip2cUpdateProgressBar = mkStdProgressBarForStatusBar();
867  d->ip2cUpdateProgressBar->setFormat(tr("IP2C Update"));
868  d->ip2cUpdateProgressBar->hide();
869  d->ip2cUpdateProgressBar->setMaximumWidth(PROGRESSBAR_WIDTH);
870  d->ip2cUpdateProgressBar->setMinimumWidth(PROGRESSBAR_WIDTH);
871  statusBar()->addPermanentWidget(d->ip2cUpdateProgressBar);
872 }
873 
874 void MainWindow::initIRCDock()
875 {
876  d->ircDock = new IRCDock(this);
877  d->menuView->addAction(d->ircDock->toggleViewAction());
878  d->ircDock->toggleViewAction()->setText(tr("&IRC"));
879  d->ircDock->toggleViewAction()->setShortcut(tr("CTRL+I"));
880  d->ircDock->hide();
881  this->addDockWidget(Qt::BottomDockWidgetArea, d->ircDock);
882 
883  if (ChatNetworksCfg().isAnyNetworkOnAutoJoin())
884  {
885  this->d->ircDock->setVisible(true);
886  this->d->ircDock->performNetworkAutojoins();
887  }
888 }
889 
890 void MainWindow::initLogDock()
891 {
892  d->logDock = new LogDock(this);
893  d->menuView->addAction(d->logDock->toggleViewAction());
894  d->logDock->toggleViewAction()->setText(tr("&Log"));
895  d->logDock->toggleViewAction()->setShortcut(tr("CTRL+L"));
896  d->logDock->hide();
897  this->addDockWidget(Qt::BottomDockWidgetArea, d->logDock);
898 
899  connect(&gLog, SIGNAL( newEntry(const QString&) ), d->logDock, SLOT( appendLogEntry(const QString&) ) );
900 
901  // Also add anything that already might be in the log to the box.
902  d->logDock->appendLogEntry(gLog.content());
903 }
904 
905 void MainWindow::initMainDock()
906 {
907  setDockNestingEnabled(true); // This line allows us to essentually treat a dock as a central widget.
908 
909  // Make a dock out of the central MainWindow widget and drop that widget
910  // from the MainWindow itself.
911  d->mainDock = new QDockWidget(tr("Servers"));
912  d->mainDock->setTitleBarWidget(new QWidget(this));
913  d->mainDock->setObjectName("ServerList");
914  d->mainDock->setFeatures(QDockWidget::NoDockWidgetFeatures);
915  d->mainDock->setWidget(centralWidget());
916  setCentralWidget(0);
917  addDockWidget(Qt::RightDockWidgetArea, d->mainDock);
918 }
919 
920 void MainWindow::initServerDetailsDock()
921 {
922  d->detailsDock = new ServerDetailsDock(this);
923  d->menuView->addAction(d->detailsDock->toggleViewAction());
924  d->detailsDock->toggleViewAction()->setText(tr("Server &Details"));
925  d->detailsDock->toggleViewAction()->setShortcut(tr("CTRL+D"));
926  d->detailsDock->hide();
927  addDockWidget(Qt::BottomDockWidgetArea, d->detailsDock);
928 
929  d->detailsDock->connect(d->serverList, SIGNAL( serversSelected(QList<ServerPtr>&) ), SLOT( displaySelection(QList<ServerPtr> &) ));
930 }
931 
932 void MainWindow::initServerFilterDock()
933 {
934  d->serverFilterDock = new ServerFilterDock(this);
935 
936  d->menuView->addAction(d->serverFilterDock->toggleViewAction());
937  d->serverFilterDock->hide();
938  this->addDockWidget(Qt::RightDockWidgetArea, d->serverFilterDock);
939 }
940 
942 {
943  bool isEnabled = gConfig.doomseeker.bUseTrayIcon;
944  if (!isEnabled || !QSystemTrayIcon::isSystemTrayAvailable())
945  {
946  if (d->trayIcon != NULL)
947  {
948  delete d->trayIcon;
949  d->trayIcon = NULL;
950  }
951 
952  if (d->trayIconMenu != NULL)
953  {
954  delete d->trayIconMenu;
955  d->trayIconMenu = NULL;
956  }
957  }
958  else if (d->trayIcon == NULL)
959  {
960  QAction* trayAction;
961  d->trayIconMenu = new QMenu(this);
962  trayAction = d->trayIconMenu->addAction("Exit");
963  connect(trayAction, SIGNAL( triggered() ), this, SLOT( quitProgram() ) );
964 
965  // This should be automatically deleted when main window closes
966  d->trayIcon = new QSystemTrayIcon(this);
967  connect(d->trayIcon, SIGNAL( activated(QSystemTrayIcon::ActivationReason) ), this, SLOT( trayIcon_activated(QSystemTrayIcon::ActivationReason) ) );
968 
969  updateTrayIconTooltipAndLogTotalRefresh();
970 
971  d->trayIcon->setContextMenu(d->trayIconMenu);
972  d->trayIcon->setIcon(QIcon(":/icon.png"));
973  d->trayIcon->setVisible(true);
974  }
975 }
976 
977 void MainWindow::ip2cDownloadProgress(qint64 current, qint64 max)
978 {
979  d->ip2cUpdateProgressBar->setMaximum(max);
980  d->ip2cUpdateProgressBar->setValue(current);
981 }
982 
983 void MainWindow::ip2cJobsFinished()
984 {
985  d->menuActionUpdateIP2C->setEnabled(true);
986  d->serverList->updateCountryFlags();
987  d->ip2cUpdateProgressBar->hide();
988 
989  if (d->ip2cLoader != NULL)
990  {
991  delete d->ip2cLoader;
992  d->ip2cLoader = NULL;
993  }
994 }
995 
996 void MainWindow::ip2cStartUpdate()
997 {
998  if (d->ip2cLoader != NULL)
999  {
1000  // If update is currently in progress then prevent re-starting.
1001  return;
1002  }
1003 
1004  d->menuActionUpdateIP2C->setEnabled(false);
1005  d->ip2cUpdateProgressBar->show();
1006 
1007  d->ip2cLoader = new IP2CLoader();
1008  connectIP2CLoader(d->ip2cLoader);
1009  d->ip2cLoader->update();
1010 }
1011 
1013 {
1014  return hasCustomServers() || isAnyMasterEnabled();
1015 }
1016 
1017 bool MainWindow::isAnyMasterEnabled() const
1018 {
1019  for (int i = 0; i < d->masterManager->numMasters(); ++i)
1020  {
1021  MasterClient* pMaster = (*d->masterManager)[i];
1022 
1023  if (pMaster->isEnabled())
1024  {
1025  return true;
1026  }
1027  }
1028 
1029  return false;
1030 }
1031 
1032 bool MainWindow::isEffectivelyActiveWindow() const
1033 {
1034  return this->isActiveWindow() || DoomseekerConfigurationDialog::isOpen();
1035 }
1036 
1037 void MainWindow::masterManagerMessages(MasterClient* pSender, const QString& title, const QString& content, bool isError)
1038 {
1039  QString message = tr("Master server for %1: %2").arg(pSender->plugin()->data()->name).arg(content);
1040 
1041  if (isError)
1042  {
1043  message = tr("Error: %1").arg(message);
1044  statusBar()->showMessage(message);
1045  }
1046 
1047  gLog << message;
1048 }
1049 
1050 void MainWindow::masterManagerMessagesImportant(MasterClient* pSender, const Message& objMessage)
1051 {
1052  QString strFullMessage = tr("%1: %2")
1053  .arg(pSender->plugin()->data()->name)
1054  .arg(objMessage.contents());
1055  d->importantMessagesWidget->addMessage(strFullMessage, objMessage.timestamp());
1056 }
1057 
1058 void MainWindow::menuBuddies()
1059 {
1060  d->buddiesList->setVisible(!d->buddiesList->isVisible());
1061 }
1062 
1063 void MainWindow::menuCreateServer()
1064 {
1065  // This object will auto-delete on close.
1066  CreateServerDialog* dialog = new CreateServerDialog(NULL);
1067  dialog->setWindowIcon(this->windowIcon());
1068  dialog->show();
1069 }
1070 
1071 void MainWindow::menuHelpAbout()
1072 {
1073  AboutDialog dlg(this);
1074  d->autoRefreshTimer.stop();
1075  dlg.exec();
1076  initAutoRefreshTimer();
1077 }
1078 
1079 void MainWindow::menuHelpHelp()
1080 {
1081  if (HELP_SITE_URL.isEmpty() || !Strings::isUrlSafe(HELP_SITE_URL))
1082  {
1083  QMessageBox::critical(this, tr("Help error"), tr("No help found."), QMessageBox::Ok, QMessageBox::Ok);
1084  return;
1085  }
1086 
1087  bool bSuccess = QDesktopServices::openUrl(HELP_SITE_URL);
1088 
1089  if (!bSuccess)
1090  {
1091  QMessageBox::critical(this, tr("Help error"), tr("Failed to open URL:\n%1").arg(HELP_SITE_URL), QMessageBox::Ok, QMessageBox::Ok);
1092  return;
1093  }
1094 }
1095 
1096 void MainWindow::menuIRCOptions()
1097 {
1098  IRCConfigurationDialog dialog(this);
1099  dialog.initOptionsList();
1100  dialog.exec();
1101 
1102  if (d->ircDock != NULL)
1103  {
1104  d->ircDock->applyAppearanceSettings();
1105 
1106  // This could probably be optimized to not re-read files from drive
1107  // if audio options didn't change but currently there are only two
1108  // files, so no harm should be done.
1109  d->ircDock->sounds().loadFromConfig();
1110  }
1111 }
1112 
1113 void MainWindow::menuLog()
1114 {
1115  d->logDock->setVisible(!d->logDock->isVisible());
1116 }
1117 
1118 void MainWindow::menuManageDemos()
1119 {
1120  DemoManagerDlg dm;
1121  dm.setWindowIcon(this->windowIcon());
1122  dm.exec();
1123 }
1124 
1125 void MainWindow::menuOptionsConfigure()
1126 {
1127  DoomseekerConfigurationDialog::openConfiguration(this);
1128 }
1129 
1130 void MainWindow::menuRecordDemo()
1131 {
1132  gConfig.doomseeker.bRecordDemo = d->menuActionRecordDemo->isChecked();
1133 }
1134 
1135 void MainWindow::menuUpdateIP2C()
1136 {
1137  IP2CUpdateBox updateBox(this);
1138 
1139  connect(&updateBox, SIGNAL( accepted() ), this, SLOT( ip2cStartUpdate() ) );
1140 
1141  updateBox.exec();
1142 }
1143 
1144 void MainWindow::menuViewIRC()
1145 {
1146  d->ircDock->setVisible(!d->ircDock->isVisible());
1147 }
1148 
1149 void MainWindow::menuWadSeeker()
1150 {
1151  if (gWadseekerShow->checkWadseekerValidity(this))
1152  {
1153  WadseekerInterface *wadseeker = WadseekerInterface::create(NULL);
1154  wadseeker->setAttribute(Qt::WA_DeleteOnClose);
1155  wadseeker->show();
1156  }
1157 }
1158 
1159 QProgressBar* MainWindow::mkStdProgressBarForStatusBar()
1160 {
1161  QProgressBar* pBar = new QProgressBar(statusBar());
1162  pBar->setAlignment(Qt::AlignCenter);
1163  pBar->setTextVisible(true);
1164  pBar->setSizePolicy(QSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed));
1165  return pBar;
1166 }
1167 
1168 void MainWindow::notifyFirstRun()
1169 {
1170  // On first run prompt configuration box.
1171  QMessageBox::information(NULL, tr("Welcome to Doomseeker"),
1172  tr("Before you start browsing for servers, please ensure that Doomseeker is properly configured."));
1173  d->menuActionConfigure->trigger();
1174 }
1175 
1176 void MainWindow::onAutoUpdaterDownloadAndInstallConfirmationRequest()
1177 {
1178  d->updatesConfirmationWidget->show();
1179 }
1180 
1181 void MainWindow::onAutoUpdaterFileProgress(qint64 bytesReceived, qint64 bytesTotal)
1182 {
1183  d->autoUpdaterFileProgressBar->setValue(bytesReceived);
1184  d->autoUpdaterFileProgressBar->setMaximum(bytesTotal);
1185 }
1186 
1187 void MainWindow::onAutoUpdaterFinish()
1188 {
1189  showAndLogStatusMessage(tr("Program update detection & download finished with status: [%1] %2")
1190  .arg((int)d->autoUpdater->errorCode()).arg(d->autoUpdater->errorString()));
1191  gConfig.autoUpdates.bPerformUpdateOnNextRun = false;
1192  if (d->autoUpdater->errorCode() == AutoUpdater::EC_Ok)
1193  {
1194  UpdateChannel channel = UpdateChannel::fromName(gConfig.autoUpdates.updateChannelName);
1195  if (channel == *d->updateChannelOnUpdateStart)
1196  {
1197  if (!d->autoUpdater->downloadedPackagesFilenames().isEmpty())
1198  {
1199  gLog << tr("Updates will be installed on next program start.");
1200  d->updatesDownloadedWidget->show();
1201  gConfig.autoUpdates.bPerformUpdateOnNextRun = true;
1202  }
1203  }
1204  else
1205  {
1206  gLog << tr("Update channel was changed during update process. Discarding update.");
1207  }
1208  }
1209  gConfig.saveToFile();
1210  d->autoUpdaterStatusBarWidget->hide();
1211  d->updatesConfirmationWidget->hide();
1212  d->autoUpdater->deleteLater();
1213  d->autoUpdater = NULL;
1214 }
1215 
1216 void MainWindow::onAutoUpdaterOverallProgress(int current, int total,
1217  const QString& msg)
1218 {
1219  d->autoUpdaterOverallProgressBar->setValue(current);
1220  d->autoUpdaterOverallProgressBar->setMaximum(total);
1221  d->autoUpdaterOverallProgressBar->setFormat(msg);
1222 }
1223 
1224 void MainWindow::postInitAppStartup()
1225 {
1226  // Load server filter from config.
1227  d->serverFilterDock->setFilterInfo(gConfig.serverFilter.info);
1228 
1229  fixIconsDpi();
1230 
1231  // Check query on statup
1232  // Let's see if we have any plugins first. If not, display error.
1233  if (gPlugins->numPlugins() > 0)
1234  {
1235  bool bGettingServers = false;
1236  bool queryOnStartup = gConfig.doomseeker.bQueryOnStartup;
1237  if (queryOnStartup)
1238  {
1239  // If "Query on startup" option is enabled we should
1240  // attempt to refresh any masters that are enabled
1241  // in the Query menu.
1242 
1243  if (isAnyMasterEnabled())
1244  {
1245  bGettingServers = true;
1246  getServers();
1247  }
1248  else
1249  {
1250  gLog << tr("Query on startup warning: No master servers are enabled in the Query menu.");
1251  }
1252  }
1253 
1254  // If we already successfully called the getServers() method
1255  // there is no need to call refreshCustomServers().
1256  if (!bGettingServers && hasCustomServers())
1257  {
1258  // Custom servers should be refreshed no matter what.
1259  // They will not block the app in any way, there is no reason
1260  // not to refresh them.
1261  refreshCustomServers();
1262  }
1263  }
1264  else
1265  {
1266  // There are no plugins so something is really bad.
1267  // Display error message.
1268  QMessageBox::critical(NULL, tr("Doomseeker critical error"),
1269  tr("Doomseeker was unable to find any plugin libraries.\n"
1270  "Although the application will still work it will not be possible "
1271  "to fetch any server info or launch any game.\n\n"
1272  "Please check if there are any files in \"engines/\" directory.\n"
1273  "To fix this problem you may try downloading Doomseeker "
1274  "again from the site specified in the Help|About box and reinstalling "
1275  "Doomseeker."));
1276  }
1277 }
1278 
1279 QueryMenuAction* MainWindow::queryMenuActionForPlugin(const EnginePlugin* plugin)
1280 {
1281  if (plugin == NULL)
1282  {
1283  return NULL;
1284  }
1285 
1286  if (d->queryMenuPorts.contains(plugin))
1287  {
1288  return d->queryMenuPorts[plugin];
1289  }
1290 
1291  return NULL;
1292 }
1293 
1294 void MainWindow::quitProgram()
1295 {
1296  d->bWantToQuit = true;
1297  QApplication::closeAllWindows();
1298 }
1299 
1300 void MainWindow::refreshCustomServers()
1301 {
1302  foreach (const ServerPtr &server, d->serverList->servers())
1303  {
1304  if (server->isCustom())
1305  gRefresher->registerServer(server.data());
1306  }
1307 }
1308 
1309 void MainWindow::refreshServersOnList()
1310 {
1311  foreach (const ServerPtr &server, d->serverList->servers())
1312  {
1313  gRefresher->registerServer(server.data());
1314  }
1315 }
1316 
1317 void MainWindow::refreshThreadBeginsWork()
1318 {
1319  statusBar()->showMessage(tr("Querying..."));
1320  d->taskbarProgress->show();
1321 }
1322 
1323 void MainWindow::refreshThreadEndsWork()
1324 {
1325  d->toolBarGetServers->setEnabled(true);
1326 
1327  d->serverList->cleanUpRightNow();
1328  statusBar()->showMessage(tr("Done"));
1329  updateTrayIconTooltipAndLogTotalRefresh();
1330  d->taskbarProgress->hide();
1331 
1332  if (d->bTotalRefreshInProcess)
1333  {
1334  initAutoRefreshTimer();
1335  }
1336 
1337  d->bTotalRefreshInProcess = false;
1338  QList<ServerPtr> selectedServers = d->serverList->selectedServers();
1339  d->detailsDock->displaySelection(selectedServers);
1340 }
1341 
1342 void MainWindow::restartAndInstallUpdatesNow()
1343 {
1345  quitProgram();
1346 }
1347 
1348 void MainWindow::runGame(const ServerPtr &server)
1349 {
1350  if(d->connectionHandler)
1351  delete d->connectionHandler;
1352 
1353  d->connectionHandler = new ConnectionHandler(server, this);
1354  d->connectionHandler->run();
1355 }
1356 
1357 void MainWindow::setQueryPluginEnabled(const EnginePlugin* plugin, bool bEnabled)
1358 {
1359  assert(plugin != NULL);
1360 
1361  QueryMenuAction* pAction = queryMenuActionForPlugin(plugin);
1362  if (pAction != NULL)
1363  {
1364  pAction->setChecked(bEnabled);
1365  if (plugin->data()->hasMasterClient())
1366  {
1367  plugin->data()->masterClient->setEnabled(bEnabled);
1368  }
1369  if (plugin->data()->hasBroadcast())
1370  {
1371  plugin->data()->broadcast->setEnabled(bEnabled);
1372  }
1373  d->serversStatusesWidgets[plugin]->setMasterEnabledStatus(bEnabled);
1374  }
1375 }
1376 
1377 void MainWindow::serverAddedToList(const ServerPtr &pServer)
1378 {
1379  if (pServer->isKnown())
1380  {
1381  const QString& gameMode = pServer->gameMode().name();
1382  d->serverFilterDock->addGameModeToComboBox(gameMode);
1383  }
1384 }
1385 
1387 {
1388  assert(this->d->updaterInstallerErrorCode == 0 &&
1389  "MainWindow::setDisplayUpdaterProcessFailure()");
1390  this->d->updaterInstallerErrorCode = errorCode;
1391  QTimer::singleShot(0, this, SLOT(showUpdaterProcessErrorDialog()));
1392 }
1393 
1395 {
1396  assert(this->d->updaterInstallerErrorCode == 0 &&
1397  "MainWindow::setDisplayUpdateInstallerError()");
1398  this->d->updaterInstallerErrorCode = errorCode;
1399  QTimer::singleShot(0, this, SLOT(showUpdateInstallErrorDialog()));
1400 }
1401 
1402 void MainWindow::setupIcons()
1403 {
1404  QStyle& style = *QApplication::style();
1405 
1406  // File menu.
1407  d->menuActionQuit->setIcon(style.standardIcon(QStyle::SP_TitleBarCloseButton));
1408 
1409  // Help menu.
1410  d->menuActionHelp->setIcon(style.standardIcon(QStyle::SP_MessageBoxQuestion));
1411  d->menuActionAbout->setIcon(style.standardIcon(QStyle::SP_MessageBoxInformation));
1412 }
1413 
1414 void MainWindow::setupToolBar()
1415 {
1416  QToolBar* pToolBar = new QToolBar(tr("Main Toolbar"), this);
1417  pToolBar->setMovable(false);
1418  pToolBar->setObjectName("Toolbar");
1419 
1420  // Refresh buttons
1421  d->toolBarGetServers = new QAction(QIcon(":/icons/refresh.png"), tr("Get Servers"), pToolBar);
1422 
1423  // Setup menu
1424  // Refresh buttons
1425  pToolBar->addAction(d->toolBarGetServers);
1426 
1427  // File menu buttons.
1428  pToolBar->addSeparator();
1429  pToolBar->addAction(d->menuActionCreateServer);
1430  pToolBar->addAction(d->menuActionWadseeker);
1431 
1432  // Demo buttons
1433  pToolBar->addSeparator();
1434  pToolBar->addAction(d->menuActionManageDemos);
1435  pToolBar->addAction(d->menuActionRecordDemo);
1436 
1437  pToolBar->addSeparator();
1438 
1439  // Dockable windows buttons.
1440  pToolBar->addAction(d->buddiesList->toggleViewAction());
1441  pToolBar->addAction(d->logDock->toggleViewAction());
1442  pToolBar->addAction(d->ircDock->toggleViewAction());
1443  pToolBar->addAction(d->serverFilterDock->toggleViewAction());
1444  pToolBar->addAction(d->detailsDock->toggleViewAction());
1445 
1446  // Quick Search
1447  QLineEdit *qs = d->serverFilterDock->createQuickSearch();
1448  qs->setSizePolicy(QSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed));
1449  qs->setMinimumWidth(175);
1450  qs->setMaximumWidth(175);
1451 
1452  QWidget* searchSeparator = new QWidget();
1453  searchSeparator->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::MinimumExpanding);
1454  pToolBar->addWidget(searchSeparator);
1455  pToolBar->addWidget(new QLabel(tr("Search:"), pToolBar));
1456  pToolBar->addWidget(qs);
1457 
1458  this->addToolBar(Qt::TopToolBarArea, pToolBar);
1459  setUnifiedTitleAndToolBarOnMac(true);
1460  connect(pToolBar, SIGNAL( actionTriggered(QAction*) ), this, SLOT( toolBarAction(QAction*) ) );
1461 }
1462 
1463 void MainWindow::showAndLogStatusMessage(const QString &message)
1464 {
1465  gLog << message;
1466  statusBar()->showMessage(message);
1467 }
1468 
1469 void MainWindow::showEvent(QShowEvent *event)
1470 {
1471 #if QT_VERSION >= 0x050000
1472  // http://stackoverflow.com/a/26910648/1089357
1473  d->taskbarButton->setWindow(windowHandle());
1474 #endif
1475 }
1476 
1477 void MainWindow::showInstallFreedoomDialog()
1478 {
1479  if (!d->freedoomDialog.isNull())
1480  {
1481  d->freedoomDialog->activateWindow();
1482  return;
1483  }
1484  FreedoomDialog *dialog = new FreedoomDialog(NULL);
1485  dialog->setAttribute(Qt::WA_DeleteOnClose);
1486  dialog->show();
1487  d->freedoomDialog = dialog;
1488 }
1489 
1490 void MainWindow::showProgramArgsHelp()
1491 {
1492  ProgramArgsHelpDialog *dialog = new ProgramArgsHelpDialog(this);
1493  dialog->setAttribute(Qt::WA_DeleteOnClose);
1494  dialog->show();
1495 }
1496 
1497 void MainWindow::showServerJoinCommandLine(const ServerPtr &server)
1498 {
1499  CommandLineInfo cli;
1500  ConnectionHandler connectionHandler(server, this);
1501  GameDemo demo = gConfig.doomseeker.bRecordDemo ? GameDemo::Unmanaged : GameDemo::NoDemo;
1502  JoinCommandLineBuilder *builder = new JoinCommandLineBuilder(server, demo, this);
1503  this->connect(builder, SIGNAL(commandLineBuildFinished()), SLOT(showServerJoinCommandLineOnBuilderFinished()));
1504  builder->obtainJoinCommandLine();
1505 }
1506 
1507 void MainWindow::showServerJoinCommandLineOnBuilderFinished()
1508 {
1509  JoinCommandLineBuilder *builder = static_cast<JoinCommandLineBuilder*>(sender());
1510  CommandLineInfo cli = builder->builtCommandLine();
1511  if (cli.isValid())
1512  {
1513  QString execPath = cli.executable.absoluteFilePath();
1514  QStringList args = cli.args;
1515 
1518 
1519  CopyTextDlg ctd(execPath + " " + args.join(" "), builder->server()->name(), this);
1520  ctd.exec();
1521  }
1522  else
1523  {
1524  if (!builder->error().isEmpty())
1525  {
1526  QMessageBox::critical(this, tr("Doomseeker - show join command line"),
1527  tr("Command line cannot be built:\n%1").arg(builder->error()));
1528  }
1529  }
1530  builder->deleteLater();
1531 }
1532 
1533 void MainWindow::showUpdaterProcessErrorDialog()
1534 {
1535  QString explanation;
1536  if (this->d->updaterInstallerErrorCode != UpdateInstaller::PEC_GeneralFailure)
1537  {
1538  QString errorCodeExplanation = UpdateInstaller::processErrorCodeToStr(
1539  (UpdateInstaller::ProcessErrorCode) this->d->updaterInstallerErrorCode);
1540  explanation = tr("Update installation problem:\n%1").arg(errorCodeExplanation);
1541  }
1542  else
1543  {
1544  explanation = tr("Update installation failed.");
1545  }
1546  QMessageBox::critical(this, tr("Doomseeker - Auto Update problem"),
1547  tr("%1\n\nRemaining updates have been discarded.").arg(explanation));
1548 }
1549 
1550 void MainWindow::showUpdateInstallErrorDialog()
1551 {
1552  QString msg = tr("Update install problem:\n%1\n\nRemaining updates have been discarded.")
1553  .arg(UpdateInstaller::errorCodeToStr(
1554  (UpdateInstaller::ErrorCode)this->d->updaterInstallerErrorCode));
1555  QMessageBox::critical(this, tr("Doomseeker - Auto Update problem"), msg);
1556 }
1557 
1558 void MainWindow::stopAutoRefreshTimer()
1559 {
1560  d->autoRefreshTimer.stop();
1561 }
1562 
1564 {
1565  QueryMenuAction* pAction = queryMenuActionForPlugin(plugin);
1566  assert(pAction != NULL);
1567 
1568  setQueryPluginEnabled(plugin, !pAction->isChecked());
1569 }
1570 
1571 void MainWindow::toolBarAction(QAction* pAction)
1572 {
1573  if (pAction == d->toolBarGetServers)
1574  {
1575  getServers();
1576  }
1577 }
1578 
1579 void MainWindow::trayIcon_activated(QSystemTrayIcon::ActivationReason reason)
1580 {
1581  if (reason == QSystemTrayIcon::Trigger)
1582  {
1583  if (isMinimized() || !isVisible())
1584  {
1585  d->bWasMaximized == true ? showMaximized() : showNormal();
1586  activateWindow();
1587  }
1588  else if (gConfig.doomseeker.bCloseToTrayIcon)
1589  {
1590  close();
1591  }
1592  else
1593  {
1594  showMinimized();
1595  }
1596  }
1597 }
1598 
1599 void MainWindow::updateDynamicAppearance()
1600 {
1601  d->tableServers->setShowGrid(gConfig.doomseeker.bDrawGridInServerTable);
1602  d->serverList->redraw();
1603  d->serverList->cleanUpForce();
1604 }
1605 
1606 // NOTE: Probably would be better if the master manager wasn't tied to the
1607 // MainWindow class?
1608 void MainWindow::updateMasterAddresses()
1609 {
1610  for(int i = 0;i < d->masterManager->numMasters();i++)
1611  {
1612  (*d->masterManager)[i]->updateAddress();
1613  }
1614 }
1615 
1616 void MainWindow::updateServerFilter(const ServerListFilterInfo& filterInfo)
1617 {
1618  d->serverList->applyFilter(filterInfo);
1619  d->lblServerFilterApplied->setVisible(filterInfo.isFilteringAnything());
1620 }
1621 
1622 ServerListCount MainWindow::sumServerListCount() const
1623 {
1624  ServerListCount count;
1625  foreach (const ServersStatusWidget *status, d->serversStatusesWidgets.values())
1626  {
1627  count += status->count();
1628  }
1629  return count;
1630 }
1631 
1632 void MainWindow::updateRefreshProgress()
1633 {
1634  ServerListCount count = sumServerListCount();
1635  d->taskbarProgress->setMaximum(count.numServers);
1636  d->taskbarProgress->setValue(count.numServers - count.numRefreshing);
1637  updateTrayIconTooltip(count);
1638 }
1639 
1640 void MainWindow::updateTrayIconTooltip(const ServerListCount &count)
1641 {
1642  if (d->trayIcon != NULL)
1643  {
1644  QString tip;
1645  tip += tr("Generic servers: %1\n").arg(count.numGenericServers);
1646  tip += tr("Custom servers: %1\n").arg(count.numCustomServers);
1647  tip += tr("LAN servers: %1\n").arg(count.numLanServers);
1648  tip += tr("Human players: %1").arg(count.numHumanPlayers);
1649  d->trayIcon->setToolTip(tip);
1650  }
1651 }
1652 
1653 void MainWindow::updateTrayIconTooltipAndLogTotalRefresh()
1654 {
1655  ServerListCount count = sumServerListCount();
1656  updateTrayIconTooltip(count);
1657 
1658  if (d->bTotalRefreshInProcess)
1659  {
1660  gLog << tr("Finished refreshing. Servers on the list: %1 "
1661  "(+%2 custom, +%3 LAN). Players: %4.")
1662  .arg(count.numGenericServers).arg(count.numCustomServers)
1663  .arg(count.numLanServers).arg(count.numHumanPlayers);
1664  }
1665 }
void initTrayIcon()
Definition: mainwindow.cpp:941
static void loadImages(const QString &style)
Structure describing server filter.
void checkForUpdatesAuto()
Auto triggered updates will display install confirmation only if configured to.
Definition: mainwindow.cpp:481
Performs a case-insensitive (OS independent) file searches.
Definition: pathfinder.h:81
Dockable widget designed for IRC communication.
Definition: ircdock.h:39
PWAD hosted on a server.
Dockable widget designed for application&#39;s log presentation.
Definition: logdock.h:33
static UpdateChannel fromName(const QString &name)
Creates object from its internal name.
void togglePluginQueryEnabled(const EnginePlugin *plugin)
Structure holding parameters for application launch.
Definition: apprunner.h:37
Message object used to pass messages throughout the Doomseeker&#39;s system.
Definition: message.h:63
void finishConfiguration(DoomseekerConfigurationDialog &, bool)
Definition: mainwindow.cpp:674
QStringList args
launch parameters
Definition: apprunner.h:43
static bool bInstallUpdatesAndRestart
If true then program will install updates and restart instead of quitting if quit is requested...
Definition: main.h:51
bool isFilteringAnything() const
Informs if filter will apply to any server.
QVariant value() const
Extracts the value as QVariant.
ProcessErrorCode
"--update-failed" values.
static bool rmAllFiles(const QString &dirPath, const QStringList &nameFilters=QStringList())
Deletes all files in specified directory.
Definition: fileutils.cpp:83
void setQueryPluginEnabled(const EnginePlugin *pClient, bool bEnabled)
Sets query for selected MasterClient object to enabled or disabled.
Generates command line for joining specified server.
QString revision
Revision used for version comparison.
Definition: updatepackage.h:71
void checkForUpdatesUserTriggered()
User triggered updates will always display install confirmation.
Definition: mainwindow.cpp:487
IniVariable retrieveSetting(const QString &name)
Gets a variable but only if it already exists.
Definition: inisection.cpp:115
static bool isUrlSafe(const QString &url)
Unsafe URLs begin with file:// and this functions returns false for such URLs.
Definition: strings.cpp:345
unsigned timestamp() const
Generation time in seconds since UTC epoch.
Definition: message.cpp:119
Dialog window allowing user to create a game.
void setDisplayUpdaterProcessFailure(int errorCode)
This should be set if program was started with "--update-failed" argument.
void setEnabled(bool b)
Platform-agnostic wrapper for QWinTaskbarButton.
Definition: taskbarbutton.h:42
bool isAnythingToRefresh() const
Will check if refresh operation has any sense.
virtual const EnginePlugin * plugin() const =0
Platform-agnostic wrapper for QWinTaskbarProgress.
Deals with program updates/upgrades.
Definition: autoupdater.h:61
void obtainJoinCommandLine()
Runs asynchronously and emits commandLineBuildFinished() when done.
Definition: dptr.h:31
void setWads(const QStringList &wads)
Sets WADs to seek.
void fillQueryMenu(MasterManager *masterManager)
Definition: mainwindow.cpp:575
Wadseeker dialog box, only one instance is allowed.
Dialog for managing demos recorded through Doomseeker.
Definition: demomanager.h:37
Manager class for a number of MasterClient instances.
Definition: mastermanager.h:38
INI section representation.
Definition: inisection.h:40
static void escapeExecutable(QString &arg)
Escapes the executable path and handles OS X bundles.
Definition: commandline.cpp:72
void setDisplayUpdateInstallerError(int errorCode)
If set then MainWindow will display the reason for updater failure.
Wrapper for PathFinder that specializes in findings WADs.
Definition: wadpathfinder.h:76
void connectEntities()
Definition: mainwindow.cpp:538
QString contents() const
Customized displayable contents of this Message.
Definition: message.cpp:87
Abstract base for all MasterClients.
Definition: masterclient.h:49
static void escapeArgs(QStringList &args)
Escapes all characters in all strings on the list.
Definition: commandline.cpp:29
QFileInfo executable
path to the executable
Definition: apprunner.h:45
bool isEnabled() const
QString name
Name of the package (program name or plugin name).
Definition: updatepackage.h:63
const QString & name() const
File name of the WAD.
bool isValid() const
It&#39;s valid when at least executable is set.
Definition: apprunner.cpp:31
void initAutoRefreshTimer()
Definition: mainwindow.cpp:794