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