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