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/createserver/hostmodetraits.h"
35 #include "gui/widgets/createserverdialogpage.h"
36 #include "ini/ini.h"
37 #include "ini/settingsproviderqt.h"
38 #include "plugins/engineplugin.h"
39 #include "serverapi/gamecreateparams.h"
40 #include "serverapi/gamehost.h"
41 #include "serverapi/message.h"
42 
43 #include <QFileDialog>
44 #include <QKeySequence>
45 #include <QMenuBar>
46 #include <QMessageBox>
47 #include <QScopedPointer>
48 #include <QStyle>
49 #include <QTimer>
50 
51 DClass<CreateServerDialog> : public Ui::CreateServerDialog
52 {
53 public:
54  QList<CreateServerDialogPage *> currentCustomPages;
55  EnginePlugin *currentEngine;
56  HostModeTraits hostMode;
57  DemoStore demoStore;
58 
59  QMenu *modeMenu;
60  QAction *hostModeAction;
61  QAction *offlineModeAction;
62 };
63 
64 DPointered(CreateServerDialog)
65 
66 const QString CreateServerDialog::TEMP_GAME_CONFIG_FILENAME = "/tmpserver.ini";
67 
68 CreateServerDialog::CreateServerDialog(GameCreateParams::HostMode hostMode, QWidget *parent)
69  : QDialog(parent)
70 {
72  // Have the window delete itself
73  setAttribute(Qt::WA_DeleteOnClose);
74 
75  d->currentEngine = nullptr;
76  d->hostMode = hostMode;
77  d->demoStore = DemoStore();
78 
79  d->setupUi(this);
80  setupMenu();
81 
82  applyModeToUi();
83 
84  d->generalSetupPanel->setCreateServerDialog(this);
85  d->rulesPanel->setCreateServerDialog(this);
86 
87  d->tabWidget->setObjectName("createGameTabWidget");
88  d->tabWidget->setStyleSheet("#createGameTabWidget::pane { border: 0; }");
89 
90  // This is a crude solution to the problem where message boxes appear
91  // before the actual Create Game dialog. We need to give some time
92  // for the dialog to appear. Unfortunately reimplementing
93  // QDialog::showEvent() didn't work very well.
94  QTimer::singleShot(1, this, SLOT(firstLoadConfigTimer()));
95 }
96 
97 CreateServerDialog::~CreateServerDialog()
98 {
99 }
100 
101 void CreateServerDialog::applyModeToUi()
102 {
103  d->generalSetupPanel->setupForHostMode(d->hostMode);
104  d->rulesPanel->setupForHostMode(d->hostMode);
105  initServerTab();
106 
107  d->modeMenu->setEnabled(d->hostMode.canChangeMode());
108  d->hostModeAction->setChecked(d->hostMode == GameCreateParams::Host);
109  d->offlineModeAction->setChecked(d->hostMode == GameCreateParams::Offline);
110 
111  switch (d->hostMode.mode)
112  {
113  case GameCreateParams::Demo:
114  setWindowTitle(tr("Doomseeker - Play Demo"));
115  d->btnStart->setText(tr("Play demo"));
116  break;
117  case GameCreateParams::Host:
118  setWindowTitle(tr("Doomseeker - Host Online Game"));
119  d->btnStart->setText(tr("Host server"));
120  break;
121  case GameCreateParams::Offline:
122  setWindowTitle(tr("Doomseeker - Play Offline Game"));
123  d->btnStart->setText(tr("Play"));
124  break;
125  case GameCreateParams::Remote:
126  setWindowTitle(tr("Doomseeker - Setup Remote Game"));
127  d->btnStart->setText(tr("Play online"));
128  break;
129  default:
130  setWindowTitle(tr("Doomseeker - Create Game"));
131  d->btnStart->setText(tr("Create game"));
132  break;
133  }
134 
135  d->btnCommandLine->setVisible(d->hostMode.canShowCommandLine());
136 }
137 
138 void CreateServerDialog::changeToHostMode()
139 {
140  d->hostMode = GameCreateParams::Host;
141  applyModeToUi();
142 }
143 
144 void CreateServerDialog::changeToOfflineMode()
145 {
146  d->hostMode = GameCreateParams::Offline;
147  applyModeToUi();
148 }
149 
150 bool CreateServerDialog::commandLineArguments(QString &executable, QStringList &args,
151  CommandLinePurpose purpose)
152 {
153  const QString errorCapt = tr("Doomseeker - create game");
154  if (d->currentEngine == nullptr)
155  {
156  QMessageBox::critical(nullptr, errorCapt, tr("No game selected"));
157  return false;
158  }
159 
160  GameCreateParams gameParams;
161  if (createHostInfo(gameParams, purpose))
162  {
163  CommandLineInfo cli;
164  QString error;
165 
166  GameHost *gameRunner = d->currentEngine->gameHost();
167  Message message = gameRunner->createHostCommandLine(gameParams, cli);
168 
169  delete gameRunner;
170 
171  if (message.isError())
172  {
173  QMessageBox::critical(nullptr, tr("Doomseeker - error"), message.contents());
174  return false;
175  }
176  else
177  {
178  executable = cli.executable.absoluteFilePath();
179  args = cli.args;
180  return true;
181  }
182  }
183  return false;
184 }
185 
186 bool CreateServerDialog::createHostInfo(GameCreateParams &params, CommandLinePurpose purpose)
187 {
188  params.setHostMode(d->hostMode);
189  d->generalSetupPanel->fillInParams(params);
190  d->dmflagsPanel->fillInParams(params);
191 
192  if (!fillInParamsFromPluginPages(params))
193  return false;
194 
195  d->customParamsPanel->fillInParams(params);
196  if (d->hostMode.canChangeAnyGameRules())
197  {
198  if (d->hostMode.isCreatingServer())
199  {
200  d->serverPanel->fillInParams(params);
201  }
202  d->rulesPanel->fillInParams(params);
203  }
204 
205  return createHostInfoDemoRecord(params, purpose);
206 }
207 
208 bool CreateServerDialog::createHostInfoDemoRecord(GameCreateParams &params, CommandLinePurpose purpose)
209 {
210  if (gConfig.doomseeker.bRecordDemo && d->hostMode.canRecordDemo())
211  {
212  const DemoRecord::Control demoMode = purpose == CommandLinePurpose::Run
214  if (demoMode == DemoRecord::Managed)
215  {
216  if (!d->demoStore.ensureStorageExists(this))
217  {
218  return false;
219  }
220  }
221  const QString demoPath = d->demoStore.mkDemoFullPath(demoMode, *d->currentEngine, false);
222  if (demoPath.isEmpty())
223  {
224  return false;
225  }
226 
227  params.setDemoPath(demoPath);
228  params.setDemoRecord(demoMode);
229  }
230  return true;
231 }
232 
233 GameMode CreateServerDialog::currentGameMode() const
234 {
235  return d->generalSetupPanel->currentGameMode();
236 }
237 
238 void CreateServerDialog::firstLoadConfigTimer()
239 {
240  initEngineSpecific(d->generalSetupPanel->currentPlugin());
241  QString tmpGameCfgPath = gDefaultDataPaths->programsDataDirectoryPath() + TEMP_GAME_CONFIG_FILENAME;
242 
243  QFileInfo fi(tmpGameCfgPath);
244  if (fi.exists())
245  loadConfig(tmpGameCfgPath, true);
246 }
247 
248 void CreateServerDialog::initDMFlagsTabs()
249 {
250  bool flagsAdded = d->hostMode.canChangeAnyGameRules() &&
251  d->dmflagsPanel->initDMFlagsTabs(d->currentEngine);
252  int tabIndex = d->tabWidget->indexOf(d->tabFlags);
253  if (flagsAdded && tabIndex < 0)
254  d->tabWidget->insertTab(d->tabWidget->indexOf(d->tabCustomParameters), d->tabFlags, tr("Flags"));
255  else if (!flagsAdded && tabIndex >= 0)
256  d->tabWidget->removeTab(tabIndex);
257 }
258 
259 void CreateServerDialog::initEngineSpecific(EnginePlugin *engine)
260 {
261  if (engine == d->currentEngine || engine == nullptr)
262  return;
263 
264  d->currentEngine = engine;
265 
266  d->generalSetupPanel->setupForEngine(engine);
267  initDMFlagsTabs();
268  initEngineSpecificPages(engine);
269  initServerTab();
270  initRules();
271 }
272 
273 void CreateServerDialog::initEngineSpecificPages(EnginePlugin *engine)
274 {
275  // First, get rid of the original pages.
276  for (CreateServerDialogPage *page : d->currentCustomPages)
277  delete page;
278  d->currentCustomPages.clear();
279 
280  if (!d->hostMode.canChangeAnyGameRules())
281  return;
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()
303  && d->hostMode.isSettingUpOnlineGame()
304  && d->hostMode.canChangeAnyGameRules());
305 }
306 
307 void CreateServerDialog::initRules()
308 {
309  d->rulesPanel->setupForEngine(d->currentEngine, currentGameMode());
310  d->tabWidget->setTabEnabled(d->tabWidget->indexOf(d->tabRules),
311  d->rulesPanel->isAnythingAvailable()
312  && d->hostMode.canChangeAnyGameRules());
313 }
314 
315 bool CreateServerDialog::loadConfig(const QString &filename, bool loadingPrevious)
316 {
317  QSettings settingsFile(filename, QSettings::IniFormat);
318  SettingsProviderQt settingsProvider(&settingsFile);
319  Ini ini(&settingsProvider);
320 
321  d->generalSetupPanel->loadConfig(ini, loadingPrevious);
322  d->rulesPanel->loadConfig(ini);
323  d->serverPanel->loadConfig(ini);
324  d->dmflagsPanel->loadConfig(ini);
325 
326  for (CreateServerDialogPage *page : d->currentCustomPages)
327  page->loadConfig(ini);
328 
329  d->customParamsPanel->loadConfig(ini);
330 
331  if (d->hostMode.canChangeMode())
332  {
333  d->hostMode = HostModeTraits::fromCfgName(ini.section("General")["hostMode"]);
334  applyModeToUi();
335  }
336 
337  return true;
338 }
339 
340 void CreateServerDialog::makeDemoPlaybackSetupDialog(EnginePlugin *plugin,
341  const GameDemo &demo, const QString &iwad, const QList<PickedGameFile> &wads)
342 {
343  initEngineSpecific(plugin);
344  switchToMode(GameCreateParams::Demo);
345  d->generalSetupPanel->setDemoPath(demo.demopath);
346  d->generalSetupPanel->setIwadByPath(iwad);
347  d->generalSetupPanel->setPwads(wads);
348 }
349 
350 void CreateServerDialog::makeRemoteGameSetupDialog(EnginePlugin *plugin)
351 {
352  initEngineSpecific(plugin);
353  switchToMode(GameCreateParams::Remote);
354 }
355 
356 MapListPanel *CreateServerDialog::mapListPanel()
357 {
358  return d->rulesPanel->mapListPanel();
359 }
360 
361 QString CreateServerDialog::mapName() const
362 {
363  return d->generalSetupPanel->mapName();
364 }
365 
366 QStringList CreateServerDialog::wadPaths() const
367 {
368  return d->generalSetupPanel->getAllWadPaths();
369 }
370 
371 bool CreateServerDialog::fillInParamsFromPluginPages(GameCreateParams &params)
372 {
373  for (CreateServerDialogPage *page : d->currentCustomPages)
374  {
375  if (page->validate())
376  page->fillInGameCreateParams(params);
377  else
378  {
379  // Pages must take care of displaying their own error messages.
380  d->tabWidget->setCurrentIndex(d->tabWidget->indexOf(page));
381  return false;
382  }
383  }
384  return true;
385 }
386 
387 void CreateServerDialog::runGame()
388 {
389  const QString errorCapt = tr("Doomseeker - create game");
390  if (d->currentEngine == nullptr)
391  {
392  QMessageBox::critical(nullptr, errorCapt, tr("No game selected"));
393  return;
394  }
395 
396  GameCreateParams gameParams;
397  if (createHostInfo(gameParams, CommandLinePurpose::Run))
398  {
399  QScopedPointer<GameHost> gameRunner(d->currentEngine->gameHost());
400  Message message = gameRunner->host(gameParams);
401 
402  if (message.isError())
403  {
404  QMessageBox::critical(nullptr, errorCapt, message.contents());
405  }
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->hostMode.cfgName();
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()
484 {
485  QString executable;
486  QStringList args;
487  if (commandLineArguments(executable, args, CommandLinePurpose::Show))
488  {
489  // Lines below directly modify the passed values.
490  CommandLine::escapeExecutable(executable);
492 
493  QString title = !d->hostMode.isCreatingServer() ?
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::startGame()
533 {
534  // TODO this should probably be controlled by a separate
535  // setting where the plugin would tell the box if the box can
536  // start the game or if it must let the plugin do it.
537  if (d->hostMode != GameCreateParams::Remote)
538  runGame();
539  else
540  accept();
541 }
542 
543 void CreateServerDialog::switchToMode(GameCreateParams::HostMode mode)
544 {
545  d->hostMode = mode;
546  applyModeToUi();
547 }