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