createserverdialog.cpp
1 //------------------------------------------------------------------------------
2 // createserverdialog.cpp
3 //------------------------------------------------------------------------------
4 //
5 // This library is free software; you can redistribute it and/or
6 // modify it under the terms of the GNU Lesser General Public
7 // License as published by the Free Software Foundation; either
8 // version 2.1 of the License, or (at your option) any later version.
9 //
10 // This library is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 // Lesser General Public License for more details.
14 //
15 // You should have received a copy of the GNU Lesser General Public
16 // License along with this library; if not, write to the Free Software
17 // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
18 // 02110-1301 USA
19 //
20 //------------------------------------------------------------------------------
21 // Copyright (C) 2009-2012 "Zalewa" <zalewapl@gmail.com>
22 //------------------------------------------------------------------------------
23 #include "createserverdialog.h"
24 #include "ui_createserverdialog.h"
25 
26 #include "apprunner.h"
27 #include "commandline.h"
28 #include "configuration/doomseekerconfig.h"
29 #include "copytextdlg.h"
30 #include "datapaths.h"
31 #include "gamedemo.h"
32 #include "gui/configuration/doomseekerconfigurationdialog.h"
33 #include "gui/widgets/createserverdialogpage.h"
34 #include "ini/ini.h"
35 #include "ini/settingsproviderqt.h"
36 #include "plugins/engineplugin.h"
37 #include "serverapi/gamecreateparams.h"
38 #include "serverapi/gamehost.h"
39 #include "serverapi/message.h"
40 
41 #include <cassert>
42 #include <QFileDialog>
43 #include <QKeySequence>
44 #include <QMenuBar>
45 #include <QMessageBox>
46 #include <QStyle>
47 #include <QTimer>
48 
49 DClass<CreateServerDialog> : public Ui::CreateServerDialog
50 {
51 public:
52  QList<CreateServerDialogPage *> currentCustomPages;
53  EnginePlugin *currentEngine;
54  GameCreateParams::HostMode hostMode;
55 
56  QMenu *modeMenu;
57  QAction *hostModeAction;
58  QAction *offlineModeAction;
59 
60  bool canLoadHostModeFromConfig() const
61  {
62  return hostMode == GameCreateParams::Host
63  || hostMode == GameCreateParams::Offline;
64  }
65 
66  QString hostModeCfgName() const
67  {
68  switch (hostMode)
69  {
70  default:
71  case GameCreateParams::Host: return "host";
72  case GameCreateParams::Offline: return "offline";
73  }
74  }
75 
76  GameCreateParams::HostMode hostModeFromCfgName(const QString &name)
77  {
78  if (name == "offline")
79  return GameCreateParams::Offline;
80  // Default to Host if there's any other value.
81  return GameCreateParams::Host;
82  }
83 };
84 
85 DPointered(CreateServerDialog)
86 
87 const QString CreateServerDialog::TEMP_GAME_CONFIG_FILENAME = "/tmpserver.ini";
88 
89 CreateServerDialog::CreateServerDialog(GameCreateParams::HostMode hostMode, QWidget *parent)
90  : QDialog(parent)
91 {
92  // Have the window delete itself
93  setAttribute(Qt::WA_DeleteOnClose);
94  assert(hostMode == GameCreateParams::Offline
95  || hostMode == GameCreateParams::Host
96  || hostMode == GameCreateParams::Remote);
97 
98  // Get rid of the useless '?' button from the title bar.
99  setWindowFlags(windowFlags() & (~Qt::WindowContextHelpButtonHint));
100 
101  d->currentEngine = nullptr;
102  d->hostMode = hostMode;
103 
104  d->setupUi(this);
105  setupMenu();
106 
107  applyModeToUi();
108 
109  d->generalSetupPanel->setCreateServerDialog(this);
110  d->rulesPanel->setCreateServerDialog(this);
111 
112  d->tabWidget->setObjectName("createGameTabWidget");
113  d->tabWidget->setStyleSheet("#createGameTabWidget::pane { border: 0; }");
114 
115  // This is a crude solution to the problem where message boxes appear
116  // before the actual Create Game dialog. We need to give some time
117  // for the dialog to appear. Unfortunately reimplementing
118  // QDialog::showEvent() didn't work very well.
119  QTimer::singleShot(1, this, SLOT(firstLoadConfigTimer()));
120 }
121 
122 CreateServerDialog::~CreateServerDialog()
123 {
124 }
125 
126 void CreateServerDialog::applyModeToUi()
127 {
128  d->generalSetupPanel->setupForHostMode(d->hostMode);
129  d->rulesPanel->setupForHostMode(d->hostMode);
130  initServerTab();
131 
132  d->modeMenu->setEnabled(d->hostMode != GameCreateParams::Remote);
133  d->hostModeAction->setChecked(d->hostMode == GameCreateParams::Host);
134  d->offlineModeAction->setChecked(d->hostMode == GameCreateParams::Offline);
135 
136  if (d->hostMode == GameCreateParams::Host)
137  d->btnStart->setText(tr("Host server"));
138  else
139  d->btnStart->setText(tr("Play"));
140 
141  QString windowTitle;
142  switch (d->hostMode)
143  {
144  case GameCreateParams::Remote:
145  windowTitle = tr("Doomseeker - Setup Remote Game");
146  break;
147  case GameCreateParams::Host:
148  windowTitle = tr("Doomseeker - Host Online Game");
149  break;
150  case GameCreateParams::Offline:
151  windowTitle = tr("Doomseeker - Play Offline Game");
152  break;
153  default:
154  windowTitle = tr("Doomseeker - [Unhandled Host Mode]");
155  break;
156  }
157  setWindowTitle(windowTitle);
158 
159  d->btnCommandLine->setVisible(d->hostMode != GameCreateParams::Remote);
160 }
161 
162 void CreateServerDialog::changeToHostMode()
163 {
164  d->hostMode = GameCreateParams::Host;
165  applyModeToUi();
166 }
167 
168 void CreateServerDialog::changeToOfflineMode()
169 {
170  d->hostMode = GameCreateParams::Offline;
171  applyModeToUi();
172 }
173 
174 bool CreateServerDialog::commandLineArguments(QString &executable, QStringList &args, bool offline)
175 {
176  const QString errorCapt = tr("Doomseeker - create game");
177  if (d->currentEngine == nullptr)
178  {
179  QMessageBox::critical(nullptr, errorCapt, tr("No game selected"));
180  return false;
181  }
182 
183  GameCreateParams gameParams;
184  if (createHostInfo(gameParams, offline))
185  {
186  CommandLineInfo cli;
187  QString error;
188 
189  GameHost *gameRunner = d->currentEngine->gameHost();
190  Message message = gameRunner->createHostCommandLine(gameParams, cli);
191 
192  delete gameRunner;
193 
194  if (message.isError())
195  {
196  QMessageBox::critical(nullptr, tr("Doomseeker - error"), message.contents());
197  return false;
198  }
199  else
200  {
201  executable = cli.executable.absoluteFilePath();
202  args = cli.args;
203  return true;
204  }
205  }
206  return false;
207 }
208 
209 bool CreateServerDialog::createHostInfo(GameCreateParams &params, bool offline)
210 {
211  params.setHostMode(d->hostMode);
212  d->generalSetupPanel->fillInParams(params);
213  d->dmflagsPanel->fillInParams(params);
214 
215  if (!fillInParamsFromPluginPages(params))
216  return false;
217 
218  d->customParamsPanel->fillInParams(params);
219  d->serverPanel->fillInParams(params);
220  d->rulesPanel->fillInParams(params);
221 
222  createHostInfoDemoRecord(params, offline);
223  return true;
224 }
225 
226 void CreateServerDialog::createHostInfoDemoRecord(GameCreateParams &params, bool offline)
227 {
228  if (gConfig.doomseeker.bRecordDemo && offline)
229  {
230  params.setDemoPath(GameDemo::mkDemoFullPath(GameDemo::Managed, *d->currentEngine));
231  params.setDemoRecord(GameDemo::Managed);
232  }
233 }
234 
235 GameMode CreateServerDialog::currentGameMode() const
236 {
237  return d->generalSetupPanel->currentGameMode();
238 }
239 
240 void CreateServerDialog::firstLoadConfigTimer()
241 {
242  initEngineSpecific(d->generalSetupPanel->currentPlugin());
243  QString tmpGameCfgPath = gDefaultDataPaths->programsDataDirectoryPath() + TEMP_GAME_CONFIG_FILENAME;
244 
245  QFileInfo fi(tmpGameCfgPath);
246  if (fi.exists())
247  loadConfig(tmpGameCfgPath, true);
248 }
249 
250 void CreateServerDialog::initDMFlagsTabs()
251 {
252  bool flagsAdded = d->dmflagsPanel->initDMFlagsTabs(d->currentEngine);
253  int tabIndex = d->tabWidget->indexOf(d->tabFlags);
254  if (flagsAdded && tabIndex < 0)
255  d->tabWidget->insertTab(d->tabWidget->indexOf(d->tabCustomParameters), d->tabFlags, tr("Flags"));
256  else if (!flagsAdded && tabIndex >= 0)
257  d->tabWidget->removeTab(tabIndex);
258 }
259 
260 void CreateServerDialog::initEngineSpecific(EnginePlugin *engine)
261 {
262  if (engine == d->currentEngine || engine == nullptr)
263  return;
264 
265  d->currentEngine = engine;
266 
267  d->generalSetupPanel->setupForEngine(engine);
268  initDMFlagsTabs();
269  initEngineSpecificPages(engine);
270  initServerTab();
271  initRules();
272 }
273 
274 void CreateServerDialog::initEngineSpecificPages(EnginePlugin *engine)
275 {
276  // First, get rid of the original pages.
277  for (CreateServerDialogPage *page : d->currentCustomPages)
278  delete page;
279  d->currentCustomPages.clear();
280 
281  // Add new custom pages to the dialog.
282  d->currentCustomPages = engine->createServerDialogPages(this);
283  for (CreateServerDialogPage *page : d->currentCustomPages)
284  {
285  int idxInsertAt = d->tabWidget->indexOf(d->tabCustomParameters);
286  d->tabWidget->insertTab(idxInsertAt, page, page->name());
287  }
288 }
289 
290 void CreateServerDialog::initGamemodeSpecific(const GameMode &gameMode)
291 {
292  d->rulesPanel->setupForEngine(d->currentEngine, gameMode);
293 }
294 
295 void CreateServerDialog::initServerTab()
296 {
297  if (d->currentEngine != nullptr)
298  d->serverPanel->setupForEngine(d->currentEngine);
299  d->tabWidget->setTabEnabled(d->tabWidget->indexOf(d->tabServer),
300  d->serverPanel->isAnythingAvailable() && d->hostMode != GameCreateParams::Offline);
301 }
302 
303 void CreateServerDialog::initRules()
304 {
305  d->rulesPanel->setupForEngine(d->currentEngine, currentGameMode());
306  d->tabWidget->setTabEnabled(d->tabWidget->indexOf(d->tabRules), d->rulesPanel->isAnythingAvailable());
307 }
308 
309 bool CreateServerDialog::loadConfig(const QString &filename, bool loadingPrevious)
310 {
311  QSettings settingsFile(filename, QSettings::IniFormat);
312  SettingsProviderQt settingsProvider(&settingsFile);
313  Ini ini(&settingsProvider);
314 
315  d->generalSetupPanel->loadConfig(ini, loadingPrevious);
316  d->rulesPanel->loadConfig(ini);
317  d->serverPanel->loadConfig(ini);
318  d->dmflagsPanel->loadConfig(ini);
319 
320  for (CreateServerDialogPage *page : d->currentCustomPages)
321  page->loadConfig(ini);
322 
323  d->customParamsPanel->loadConfig(ini);
324 
325  if (d->canLoadHostModeFromConfig())
326  {
327  d->hostMode = d->hostModeFromCfgName(ini.section("General")["hostMode"]);
328  applyModeToUi();
329  }
330 
331  return true;
332 }
333 
334 void CreateServerDialog::makeRemoteGameSetupDialog(EnginePlugin *plugin)
335 {
336  initEngineSpecific(plugin);
337  d->hostMode = GameCreateParams::Remote;
338  applyModeToUi();
339 }
340 
341 MapListPanel *CreateServerDialog::mapListPanel()
342 {
343  return d->rulesPanel->mapListPanel();
344 }
345 
346 QString CreateServerDialog::mapName() const
347 {
348  return d->generalSetupPanel->mapName();
349 }
350 
351 QStringList CreateServerDialog::wadPaths() const
352 {
353  return d->generalSetupPanel->getAllWadPaths();
354 }
355 
356 bool CreateServerDialog::fillInParamsFromPluginPages(GameCreateParams &params)
357 {
358  for (CreateServerDialogPage *page : d->currentCustomPages)
359  {
360  if (page->validate())
361  page->fillInGameCreateParams(params);
362  else
363  {
364  // Pages must take care of displaying their own error messages.
365  d->tabWidget->setCurrentIndex(d->tabWidget->indexOf(page));
366  return false;
367  }
368  }
369  return true;
370 }
371 
372 void CreateServerDialog::runGame(bool offline)
373 {
374  const QString errorCapt = tr("Doomseeker - create game");
375  if (d->currentEngine == nullptr)
376  {
377  QMessageBox::critical(nullptr, errorCapt, tr("No game selected"));
378  return;
379  }
380 
381  GameCreateParams gameParams;
382  if (createHostInfo(gameParams, offline))
383  {
384  QString error;
385 
386  GameHost *gameRunner = d->currentEngine->gameHost();
387  Message message = gameRunner->host(gameParams);
388 
389  delete gameRunner;
390 
391  if (message.isError())
392  QMessageBox::critical(nullptr, tr("Doomseeker - error"), message.contents());
393  else
394  {
395  QString tmpGameConfigPath = gDefaultDataPaths->programsDataDirectoryPath() + TEMP_GAME_CONFIG_FILENAME;
396  saveConfig(tmpGameConfigPath);
397  }
398  }
399 }
400 
401 bool CreateServerDialog::saveConfig(const QString &filename)
402 {
403  QSettings settingsFile(filename, QSettings::IniFormat);
404  SettingsProviderQt settingsProvider(&settingsFile);
405  Ini ini(&settingsProvider);
406 
407  d->generalSetupPanel->saveConfig(ini);
408  d->rulesPanel->saveConfig(ini);
409  d->serverPanel->saveConfig(ini);
410  d->dmflagsPanel->saveConfig(ini);
411 
412  for (CreateServerDialogPage *page : d->currentCustomPages)
413  page->saveConfig(ini);
414 
415  d->customParamsPanel->saveConfig(ini);
416 
417  ini.section("General")["hostMode"] = d->hostModeCfgName();
418 
419  if (settingsFile.isWritable())
420  {
421  settingsFile.sync();
422  return true;
423  }
424  return false;
425 }
426 
427 void CreateServerDialog::setIwadByName(const QString &iwad)
428 {
429  d->generalSetupPanel->setIwadByName(iwad);
430 }
431 
432 void CreateServerDialog::setupMenu()
433 {
434  QMenuBar *mainMenu = new QMenuBar(this);
435 
436  d->modeMenu = mainMenu->addMenu(tr("&Mode"));
437  d->hostModeAction = d->modeMenu->addAction(tr("&Host server"),
438  this, SLOT(changeToHostMode()));
439  d->hostModeAction->setCheckable(true);
440  d->offlineModeAction = d->modeMenu->addAction(tr("&Play offline"),
441  this, SLOT(changeToOfflineMode()));
442  d->offlineModeAction->setCheckable(true);
443 
444  auto *settingsMenu = mainMenu->addMenu(tr("&Settings"));
445 
446  auto *loadConfigAction = settingsMenu->addAction(tr("&Load game configuration"),
447  this, SLOT(showLoadConfig()));
448  loadConfigAction->setIcon(style()->standardIcon(QStyle::SP_DirIcon));
449  loadConfigAction->setShortcut(QKeySequence("Ctrl+O"));
450 
451  auto *saveConfigAction = settingsMenu->addAction(tr("&Save game configuration"),
452  this, SLOT(showSaveConfig()));
453  saveConfigAction->setIcon(QIcon(":/icons/diskette.png"));
454  saveConfigAction->setShortcut(QKeySequence("Ctrl+S"));
455  settingsMenu->addSeparator();
456 
457  auto *programSettings = settingsMenu->addAction(tr("&Program settings"),
458  this, SLOT(showConfiguration()));
459  programSettings->setIcon(QIcon(":/icons/preferences-system-4.png"));
460 
461  d->dialogLayout->setMenuBar(mainMenu);
462 }
463 
464 void CreateServerDialog::showConfiguration()
465 {
466  DoomseekerConfigurationDialog::openConfiguration(this, d->currentEngine);
467  d->generalSetupPanel->reloadAppConfig();
468 }
469 
470 void CreateServerDialog::showCommandLine(bool offline)
471 {
472  QString executable;
473  QStringList args;
474  if (commandLineArguments(executable, args, offline))
475  {
476  // Lines below directly modify the passed values.
477  CommandLine::escapeExecutable(executable);
479 
480  QString title = offline ?
481  tr("Run game command line:") :
482  tr("Host server command line:");
483  CopyTextDlg ctd(executable + " " + args.join(" "), title, this);
484  ctd.exec();
485  }
486 }
487 
488 void CreateServerDialog::showLoadConfig()
489 {
490  QString dialogDir = gConfig.doomseeker.previousCreateServerConfigDir;
491  QString strFile = QFileDialog::getOpenFileName(this, tr("Doomseeker - load game setup config"), dialogDir, tr("Config files (*.ini)"));
492 
493  if (!strFile.isEmpty())
494  {
495  QFileInfo fi(strFile);
496  gConfig.doomseeker.previousCreateServerConfigDir = fi.absolutePath();
497 
498  loadConfig(strFile, false);
499  }
500 }
501 
502 void CreateServerDialog::showSaveConfig()
503 {
504  QString dialogDir = gConfig.doomseeker.previousCreateServerConfigDir;
505  QString strFile = QFileDialog::getSaveFileName(this, tr("Doomseeker - save game setup config"), dialogDir, tr("Config files (*.ini)"));
506  if (!strFile.isEmpty())
507  {
508  QFileInfo fi(strFile);
509  gConfig.doomseeker.previousCreateServerConfigDir = fi.absolutePath();
510 
511  if (fi.suffix().isEmpty())
512  strFile += ".ini";
513 
514  if (!saveConfig(strFile))
515  QMessageBox::critical(nullptr, tr("Doomseeker - save game setup config"), tr("Unable to save game setup configuration!"));
516  }
517 }
518 
519 void CreateServerDialog::showStartGameCommandLine()
520 {
521  showCommandLine(d->hostMode == GameCreateParams::Offline);
522 }
523 
524 void CreateServerDialog::startGame()
525 {
526  if (d->hostMode != GameCreateParams::Remote)
527  runGame(d->hostMode == GameCreateParams::Offline);
528  else
529  accept();
530 }