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,
177  bool offline, CommandLinePurpose purpose)
178 {
179  const QString errorCapt = tr("Doomseeker - create game");
180  if (d->currentEngine == nullptr)
181  {
182  QMessageBox::critical(nullptr, errorCapt, tr("No game selected"));
183  return false;
184  }
185 
186  GameCreateParams gameParams;
187  if (createHostInfo(gameParams, offline, purpose))
188  {
189  CommandLineInfo cli;
190  QString error;
191 
192  GameHost *gameRunner = d->currentEngine->gameHost();
193  Message message = gameRunner->createHostCommandLine(gameParams, cli);
194 
195  delete gameRunner;
196 
197  if (message.isError())
198  {
199  QMessageBox::critical(nullptr, tr("Doomseeker - error"), message.contents());
200  return false;
201  }
202  else
203  {
204  executable = cli.executable.absoluteFilePath();
205  args = cli.args;
206  return true;
207  }
208  }
209  return false;
210 }
211 
212 bool CreateServerDialog::createHostInfo(GameCreateParams &params, bool offline, CommandLinePurpose purpose)
213 {
214  params.setHostMode(d->hostMode);
215  d->generalSetupPanel->fillInParams(params);
216  d->dmflagsPanel->fillInParams(params);
217 
218  if (!fillInParamsFromPluginPages(params))
219  return false;
220 
221  d->customParamsPanel->fillInParams(params);
222  d->serverPanel->fillInParams(params);
223  d->rulesPanel->fillInParams(params);
224 
225  return createHostInfoDemoRecord(params, offline, purpose);
226 }
227 
228 bool CreateServerDialog::createHostInfoDemoRecord(GameCreateParams &params, bool offline, CommandLinePurpose purpose)
229 {
230  if (gConfig.doomseeker.bRecordDemo && offline)
231  {
232  const GameDemo::Control demoMode = purpose == CommandLinePurpose::Run
233  ? GameDemo::Managed : GameDemo::Unmanaged;
234  if (demoMode == GameDemo::Managed)
235  {
236  if (!GameDemo::ensureStorageExists(this))
237  {
238  return false;
239  }
240  }
241 
242  params.setDemoPath(GameDemo::mkDemoFullPath(demoMode, *d->currentEngine));
243  params.setDemoRecord(demoMode);
244  }
245  return true;
246 }
247 
248 GameMode CreateServerDialog::currentGameMode() const
249 {
250  return d->generalSetupPanel->currentGameMode();
251 }
252 
253 void CreateServerDialog::firstLoadConfigTimer()
254 {
255  initEngineSpecific(d->generalSetupPanel->currentPlugin());
256  QString tmpGameCfgPath = gDefaultDataPaths->programsDataDirectoryPath() + TEMP_GAME_CONFIG_FILENAME;
257 
258  QFileInfo fi(tmpGameCfgPath);
259  if (fi.exists())
260  loadConfig(tmpGameCfgPath, true);
261 }
262 
263 void CreateServerDialog::initDMFlagsTabs()
264 {
265  bool flagsAdded = d->dmflagsPanel->initDMFlagsTabs(d->currentEngine);
266  int tabIndex = d->tabWidget->indexOf(d->tabFlags);
267  if (flagsAdded && tabIndex < 0)
268  d->tabWidget->insertTab(d->tabWidget->indexOf(d->tabCustomParameters), d->tabFlags, tr("Flags"));
269  else if (!flagsAdded && tabIndex >= 0)
270  d->tabWidget->removeTab(tabIndex);
271 }
272 
273 void CreateServerDialog::initEngineSpecific(EnginePlugin *engine)
274 {
275  if (engine == d->currentEngine || engine == nullptr)
276  return;
277 
278  d->currentEngine = engine;
279 
280  d->generalSetupPanel->setupForEngine(engine);
281  initDMFlagsTabs();
282  initEngineSpecificPages(engine);
283  initServerTab();
284  initRules();
285 }
286 
287 void CreateServerDialog::initEngineSpecificPages(EnginePlugin *engine)
288 {
289  // First, get rid of the original pages.
290  for (CreateServerDialogPage *page : d->currentCustomPages)
291  delete page;
292  d->currentCustomPages.clear();
293 
294  // Add new custom pages to the dialog.
295  d->currentCustomPages = engine->createServerDialogPages(this);
296  for (CreateServerDialogPage *page : d->currentCustomPages)
297  {
298  int idxInsertAt = d->tabWidget->indexOf(d->tabCustomParameters);
299  d->tabWidget->insertTab(idxInsertAt, page, page->name());
300  }
301 }
302 
303 void CreateServerDialog::initGamemodeSpecific(const GameMode &gameMode)
304 {
305  d->rulesPanel->setupForEngine(d->currentEngine, gameMode);
306 }
307 
308 void CreateServerDialog::initServerTab()
309 {
310  if (d->currentEngine != nullptr)
311  d->serverPanel->setupForEngine(d->currentEngine);
312  d->tabWidget->setTabEnabled(d->tabWidget->indexOf(d->tabServer),
313  d->serverPanel->isAnythingAvailable() && d->hostMode != GameCreateParams::Offline);
314 }
315 
316 void CreateServerDialog::initRules()
317 {
318  d->rulesPanel->setupForEngine(d->currentEngine, currentGameMode());
319  d->tabWidget->setTabEnabled(d->tabWidget->indexOf(d->tabRules), d->rulesPanel->isAnythingAvailable());
320 }
321 
322 bool CreateServerDialog::loadConfig(const QString &filename, bool loadingPrevious)
323 {
324  QSettings settingsFile(filename, QSettings::IniFormat);
325  SettingsProviderQt settingsProvider(&settingsFile);
326  Ini ini(&settingsProvider);
327 
328  d->generalSetupPanel->loadConfig(ini, loadingPrevious);
329  d->rulesPanel->loadConfig(ini);
330  d->serverPanel->loadConfig(ini);
331  d->dmflagsPanel->loadConfig(ini);
332 
333  for (CreateServerDialogPage *page : d->currentCustomPages)
334  page->loadConfig(ini);
335 
336  d->customParamsPanel->loadConfig(ini);
337 
338  if (d->canLoadHostModeFromConfig())
339  {
340  d->hostMode = d->hostModeFromCfgName(ini.section("General")["hostMode"]);
341  applyModeToUi();
342  }
343 
344  return true;
345 }
346 
347 void CreateServerDialog::makeRemoteGameSetupDialog(EnginePlugin *plugin)
348 {
349  initEngineSpecific(plugin);
350  d->hostMode = GameCreateParams::Remote;
351  applyModeToUi();
352 }
353 
354 MapListPanel *CreateServerDialog::mapListPanel()
355 {
356  return d->rulesPanel->mapListPanel();
357 }
358 
359 QString CreateServerDialog::mapName() const
360 {
361  return d->generalSetupPanel->mapName();
362 }
363 
364 QStringList CreateServerDialog::wadPaths() const
365 {
366  return d->generalSetupPanel->getAllWadPaths();
367 }
368 
369 bool CreateServerDialog::fillInParamsFromPluginPages(GameCreateParams &params)
370 {
371  for (CreateServerDialogPage *page : d->currentCustomPages)
372  {
373  if (page->validate())
374  page->fillInGameCreateParams(params);
375  else
376  {
377  // Pages must take care of displaying their own error messages.
378  d->tabWidget->setCurrentIndex(d->tabWidget->indexOf(page));
379  return false;
380  }
381  }
382  return true;
383 }
384 
385 void CreateServerDialog::runGame(bool offline)
386 {
387  const QString errorCapt = tr("Doomseeker - create game");
388  if (d->currentEngine == nullptr)
389  {
390  QMessageBox::critical(nullptr, errorCapt, tr("No game selected"));
391  return;
392  }
393 
394  GameCreateParams gameParams;
395  if (createHostInfo(gameParams, offline, CommandLinePurpose::Run))
396  {
397  QString error;
398 
399  GameHost *gameRunner = d->currentEngine->gameHost();
400  Message message = gameRunner->host(gameParams);
401 
402  delete gameRunner;
403 
404  if (message.isError())
405  QMessageBox::critical(nullptr, tr("Doomseeker - error"), message.contents());
406  else
407  {
408  QString tmpGameConfigPath = gDefaultDataPaths->programsDataDirectoryPath() + TEMP_GAME_CONFIG_FILENAME;
409  saveConfig(tmpGameConfigPath);
410  }
411  }
412 }
413 
414 bool CreateServerDialog::saveConfig(const QString &filename)
415 {
416  QSettings settingsFile(filename, QSettings::IniFormat);
417  SettingsProviderQt settingsProvider(&settingsFile);
418  Ini ini(&settingsProvider);
419 
420  d->generalSetupPanel->saveConfig(ini);
421  d->rulesPanel->saveConfig(ini);
422  d->serverPanel->saveConfig(ini);
423  d->dmflagsPanel->saveConfig(ini);
424 
425  for (CreateServerDialogPage *page : d->currentCustomPages)
426  page->saveConfig(ini);
427 
428  d->customParamsPanel->saveConfig(ini);
429 
430  ini.section("General")["hostMode"] = d->hostModeCfgName();
431 
432  if (settingsFile.isWritable())
433  {
434  settingsFile.sync();
435  return true;
436  }
437  return false;
438 }
439 
440 void CreateServerDialog::setIwadByName(const QString &iwad)
441 {
442  d->generalSetupPanel->setIwadByName(iwad);
443 }
444 
445 void CreateServerDialog::setupMenu()
446 {
447  QMenuBar *mainMenu = new QMenuBar(this);
448 
449  d->modeMenu = mainMenu->addMenu(tr("&Mode"));
450  d->hostModeAction = d->modeMenu->addAction(tr("&Host server"),
451  this, SLOT(changeToHostMode()));
452  d->hostModeAction->setCheckable(true);
453  d->offlineModeAction = d->modeMenu->addAction(tr("&Play offline"),
454  this, SLOT(changeToOfflineMode()));
455  d->offlineModeAction->setCheckable(true);
456 
457  auto *settingsMenu = mainMenu->addMenu(tr("&Settings"));
458 
459  auto *loadConfigAction = settingsMenu->addAction(tr("&Load game configuration"),
460  this, SLOT(showLoadConfig()));
461  loadConfigAction->setIcon(style()->standardIcon(QStyle::SP_DirIcon));
462  loadConfigAction->setShortcut(QKeySequence("Ctrl+O"));
463 
464  auto *saveConfigAction = settingsMenu->addAction(tr("&Save game configuration"),
465  this, SLOT(showSaveConfig()));
466  saveConfigAction->setIcon(QIcon(":/icons/diskette.png"));
467  saveConfigAction->setShortcut(QKeySequence("Ctrl+S"));
468  settingsMenu->addSeparator();
469 
470  auto *programSettings = settingsMenu->addAction(tr("&Program settings"),
471  this, SLOT(showConfiguration()));
472  programSettings->setIcon(QIcon(":/icons/preferences-system-4.png"));
473 
474  d->dialogLayout->setMenuBar(mainMenu);
475 }
476 
477 void CreateServerDialog::showConfiguration()
478 {
479  DoomseekerConfigurationDialog::openConfiguration(this, d->currentEngine);
480  d->generalSetupPanel->reloadAppConfig();
481 }
482 
483 void CreateServerDialog::showCommandLine(bool offline)
484 {
485  QString executable;
486  QStringList args;
487  if (commandLineArguments(executable, args, offline, CommandLinePurpose::Show))
488  {
489  // Lines below directly modify the passed values.
490  CommandLine::escapeExecutable(executable);
492 
493  QString title = offline ?
494  tr("Run game command line:") :
495  tr("Host server command line:");
496  CopyTextDlg ctd(executable + " " + args.join(" "), title, this);
497  ctd.exec();
498  }
499 }
500 
501 void CreateServerDialog::showLoadConfig()
502 {
503  QString dialogDir = gConfig.doomseeker.previousCreateServerConfigDir;
504  QString strFile = QFileDialog::getOpenFileName(this, tr("Doomseeker - load game setup config"), dialogDir, tr("Config files (*.ini)"));
505 
506  if (!strFile.isEmpty())
507  {
508  QFileInfo fi(strFile);
509  gConfig.doomseeker.previousCreateServerConfigDir = fi.absolutePath();
510 
511  loadConfig(strFile, false);
512  }
513 }
514 
515 void CreateServerDialog::showSaveConfig()
516 {
517  QString dialogDir = gConfig.doomseeker.previousCreateServerConfigDir;
518  QString strFile = QFileDialog::getSaveFileName(this, tr("Doomseeker - save game setup config"), dialogDir, tr("Config files (*.ini)"));
519  if (!strFile.isEmpty())
520  {
521  QFileInfo fi(strFile);
522  gConfig.doomseeker.previousCreateServerConfigDir = fi.absolutePath();
523 
524  if (fi.suffix().isEmpty())
525  strFile += ".ini";
526 
527  if (!saveConfig(strFile))
528  QMessageBox::critical(nullptr, tr("Doomseeker - save game setup config"), tr("Unable to save game setup configuration!"));
529  }
530 }
531 
532 void CreateServerDialog::showStartGameCommandLine()
533 {
534  showCommandLine(d->hostMode == GameCreateParams::Offline);
535 }
536 
537 void CreateServerDialog::startGame()
538 {
539  if (d->hostMode != GameCreateParams::Remote)
540  runGame(d->hostMode == GameCreateParams::Offline);
541  else
542  accept();
543 }