swift
settingssimulatorbasicscomponent.cpp
1 // SPDX-FileCopyrightText: Copyright (C) 2016 swift Project Community / Contributors
2 // SPDX-License-Identifier: GPL-3.0-or-later OR LicenseRef-swift-pilot-client-1
3 
5 
6 #include <QMessageBox>
7 #include <QRegularExpression>
8 
9 #include "ui_settingssimulatorbasicscomponent.h"
10 
11 #include "config/buildconfig.h"
12 #include "misc/fileutils.h"
13 #include "misc/htmlutils.h"
14 #include "misc/logmessage.h"
16 
17 using namespace swift::misc;
18 using namespace swift::misc::simulation;
19 using namespace swift::misc::simulation::fscommon;
20 using namespace swift::misc::simulation::settings;
21 using namespace swift::misc::simulation::xplane;
22 using namespace swift::config;
23 
24 namespace swift::gui::components
25 {
26  const QStringList &CSettingsSimulatorBasicsComponent::getLogCategories()
27  {
28  static const QStringList cats({ CLogCategories::guiComponent(), CLogCategories::wizard() });
29  return cats;
30  }
31 
32  CSettingsSimulatorBasicsComponent::CSettingsSimulatorBasicsComponent(QWidget *parent)
34  {
35  ui->setupUi(this);
36  this->setSmallLayout(true); // no disadvantage, so I always set it
37  ui->comp_SimulatorSelector->setMode(CSimulatorSelector::RadioButtons);
38  ui->comp_SimulatorSelector->setRememberSelectionAndSetToLastSelection();
39 
40  connect(ui->pb_ExcludeFileDialog, &QPushButton::clicked, this,
41  &CSettingsSimulatorBasicsComponent::excludeFileDialog);
42  connect(ui->pb_ModelFileDialog, &QPushButton::clicked, this,
43  &CSettingsSimulatorBasicsComponent::modelFileDialog);
44  connect(ui->pb_SimulatorFileDialog, &QPushButton::clicked, this,
45  &CSettingsSimulatorBasicsComponent::simulatorFileDialog);
46  connect(ui->pb_Save, &QPushButton::clicked, this, &CSettingsSimulatorBasicsComponent::save);
47  connect(ui->pb_Reset, &QPushButton::clicked, this, &CSettingsSimulatorBasicsComponent::reset);
48  connect(ui->pb_CopyDefaults, &QPushButton::clicked, this, &CSettingsSimulatorBasicsComponent::copyDefaults);
49  connect(ui->pb_AdjustModelDirectory, &QPushButton::clicked, this,
50  &CSettingsSimulatorBasicsComponent::adjustModelDirectory);
51  connect(ui->le_SimulatorDirectory, &QLineEdit::returnPressed, this,
52  &CSettingsSimulatorBasicsComponent::simulatorDirectoryEntered);
53  connect(ui->comp_SimulatorSelector, &CSimulatorSelector::changed, this,
54  &CSettingsSimulatorBasicsComponent::onSimulatorChanged);
55  connect(&m_settings, &CMultiSimulatorSettings::settingsChanged, this,
56  &CSettingsSimulatorBasicsComponent::onSimulatorSettingsChanged);
57 
58  this->onSimulatorChanged();
59  }
60 
62 
63  void CSettingsSimulatorBasicsComponent::hideSelector(bool show) { ui->comp_SimulatorSelector->setVisible(show); }
64 
66  {
67  return !ui->le_SimulatorDirectory->text().isEmpty() || !ui->pte_ModelDirectories->toPlainText().isEmpty() ||
68  !ui->pte_ExcludeDirectories->toPlainText().isEmpty();
69  }
70 
72  {
73  Q_ASSERT_X(simulator.isSingleSimulator(), Q_FUNC_INFO, "Need single simulator");
74  ui->comp_SimulatorSelector->setValue(simulator);
75  }
76 
77  void CSettingsSimulatorBasicsComponent::setSmallLayout(bool small)
78  {
79  ui->lbl_ExcludeDirectories->setWordWrap(small);
80  ui->lbl_ModelDirectory->setWordWrap(small);
81  ui->lbl_SimulatorDirectory->setWordWrap(small);
82  }
83 
84  void CSettingsSimulatorBasicsComponent::simulatorFileDialog()
85  {
86  const QString startDirectory = CFileUtils::fixWindowsUncPath(this->getFileBrowserSimulatorDirectory());
87  const QString dir =
88  QFileDialog::getExistingDirectory(this, tr("Simulator directory"), startDirectory,
89  QFileDialog::ShowDirsOnly | QFileDialog::DontResolveSymlinks);
90  if (dir.isEmpty()) { return; }
91  ui->le_SimulatorDirectory->setText(CFileUtils::normalizeFilePathToQtStandard(dir));
92  this->adjustModelDirectory();
93  }
94 
95  void CSettingsSimulatorBasicsComponent::modelFileDialog()
96  {
97  const QString startDirectory = CFileUtils::fixWindowsUncPath(this->getFileBrowserModelDirectory());
98  const QString dir = QFileDialog::getExistingDirectory(
99  this, tr("Model directory"), startDirectory, QFileDialog::ShowDirsOnly | QFileDialog::DontResolveSymlinks);
100  if (dir.isEmpty()) { return; }
101  m_unsavedChanges = true;
102  const QStringList newDirs = this->addDirectory(dir, this->parseModelDirectories());
103  this->displayModelDirectories(newDirs);
104  }
105 
106  void CSettingsSimulatorBasicsComponent::excludeFileDialog()
107  {
108  const QString startDirectory = this->getFileBrowserModelDirectory();
109  const QString dir =
110  QFileDialog::getExistingDirectory(this, tr("Exclude directory"), startDirectory,
111  QFileDialog::ShowDirsOnly | QFileDialog::DontResolveSymlinks);
112  if (dir.isEmpty()) { return; }
113  m_unsavedChanges = true;
114  const QStringList newDirs =
115  CFileUtils::stripLeadingSlashOrDriveLetters(this->addDirectory(dir, this->parseExcludeDirectories()));
116  this->displayExcludeDirectoryPatterns(newDirs);
117  }
118 
119  void CSettingsSimulatorBasicsComponent::simulatorDirectoryEntered()
120  {
121  const CSimulatorInfo simulator(ui->comp_SimulatorSelector->getValue());
122  const QString simDir = CFileUtils::normalizeFilePathToQtStandard(ui->le_SimulatorDirectory->text().trimmed());
123  ui->le_SimulatorDirectory->setText(simDir);
124  this->displayDefaultValuesAsPlaceholder(simulator);
125  }
126 
128  {
129  using namespace std::chrono_literals;
130  const CSimulatorInfo simulator(ui->comp_SimulatorSelector->getValue());
131  CSimulatorSettings s = this->getSettings(simulator).getGenericSettings();
132  const QString simulatorDir(ui->le_SimulatorDirectory->text().trimmed());
133  const QStringList modelDirs(this->parseModelDirectories());
134  const QStringList excludeDirs(this->parseDirectories(ui->pte_ExcludeDirectories->toPlainText()));
135 
136  const QStringList relativeDirs = CFileUtils::makeDirectoriesRelative(
137  excludeDirs, this->getFileBrowserModelDirectory(), m_fileCaseSensitivity);
138  s.setSimulatorDirectory(simulatorDir);
139  s.setModelDirectories(modelDirs);
140  s.setModelExcludeDirectories(relativeDirs);
141  const CStatusMessageList msgs = m_settings.setAndValidateSettings(s, simulator);
142  if (msgs.isSuccess())
143  {
144  const CStatusMessage m = m_settings.setAndSaveSettings(s, simulator);
145  if (!m.isEmpty()) { CLogMessage::preformatted(m); }
146  if (m.isSuccess()) { this->showOverlayHTMLMessage("Saved settings", 5s); }
147  else { this->showOverlayMessage(m); }
148  m_unsavedChanges = m_unsavedChanges && !m.isSuccess(); // reset if success, but only if there were changes
149 
150  // display as it was saved
151  this->displaySettings(simulator);
152  }
153  else { this->showOverlayMessagesOrHTMLMessage(msgs); }
154  }
155 
156  void CSettingsSimulatorBasicsComponent::copyDefaults()
157  {
158  const CSimulatorInfo simulator(ui->comp_SimulatorSelector->getValue());
159  const bool anyValues = this->hasAnyValues();
160  if (anyValues)
161  {
162  QMessageBox::StandardButton reply = QMessageBox::question(this, "Override", "Override existing values?",
163  QMessageBox::Yes | QMessageBox::No);
164  if (reply != QMessageBox::Yes) { return; }
165  }
166 
167  // override if values are not empty
168  const CSpecializedSimulatorSettings ss = m_settings.getSpecializedSettings(simulator);
169  const QString sd = CFileUtils::fixWindowsUncPath(
170  CFileUtils::normalizeFilePathToQtStandard(ss.defaultSimulatorDirectory(simulator)));
171  if (!sd.isEmpty())
172  {
173  ui->le_SimulatorDirectory->setText(sd);
174  m_unsavedChanges = true;
175  }
176 
177  const QStringList md(m_settings.defaultModelDirectories(simulator));
178  if (!md.isEmpty())
179  {
180  this->displayModelDirectories(md);
181  m_unsavedChanges = true;
182  }
183 
184  const QStringList excludes(ss.defaultModelExcludeDirectoryPatterns(simulator));
185  if (!excludes.isEmpty())
186  {
187  this->displayExcludeDirectoryPatterns(excludes);
188  m_unsavedChanges = true;
189  }
190  }
191 
192  void CSettingsSimulatorBasicsComponent::adjustModelDirectory()
193  {
194  const CSimulatorInfo simulator(ui->comp_SimulatorSelector->getValue());
195  const QString simDir = this->getFileBrowserSimulatorDirectory();
196  CSpecializedSimulatorSettings s = m_settings.getSpecializedSettings(simulator);
197  s.setSimulatorDirectory(simDir);
198 
199  // There is not really a fixed place in the X-Plane install directory where models are put.
200  // We just treat the whole X-Plane directory as model directory and search for models in all subdirectories
201  // recursively.
202 
203  const QStringList parsedDirectories = this->parseModelDirectories();
204  const QStringList newDirs =
205  parsedDirectories.size() > 1 ?
206  parsedDirectories :
207  this->removeDirectories(s.getModelDirectoriesFromSimulatorDirectoryOrDefault(), parsedDirectories);
208  this->displayModelDirectories(newDirs);
209  }
210 
211  void CSettingsSimulatorBasicsComponent::reset()
212  {
213  const CSimulatorInfo simulator(ui->comp_SimulatorSelector->getValue());
214 
215  m_settings.resetToDefaults(simulator);
216  m_unsavedChanges = true;
217 
218  ui->le_SimulatorDirectory->clear();
219  ui->pte_ModelDirectories->clear();
220  ui->pte_ExcludeDirectories->clear();
221  this->displayDefaultValuesAsPlaceholder(simulator);
222 
223  CLogMessage(this).info(u"Reset values for settings of %1") << simulator.toQString(true);
224  }
225 
226  void CSettingsSimulatorBasicsComponent::onSimulatorChanged()
227  {
228  const CSimulatorInfo simulator(ui->comp_SimulatorSelector->getValue());
229  this->displaySettings(simulator);
230  this->displayDefaultValuesAsPlaceholder(simulator);
231  }
232 
233  void CSettingsSimulatorBasicsComponent::onSimulatorSettingsChanged(const CSimulatorInfo &simulator)
234  {
235  const CSimulatorInfo selectedSimulator(ui->comp_SimulatorSelector->getValue());
236  if (selectedSimulator == simulator) { this->displaySettings(simulator); }
237  }
238 
239  QStringList CSettingsSimulatorBasicsComponent::parseModelDirectories() const
240  {
241  return this->parseDirectories(ui->pte_ModelDirectories->toPlainText());
242  }
243 
244  QStringList CSettingsSimulatorBasicsComponent::parseExcludeDirectories() const
245  {
246  return this->parseDirectories(ui->pte_ExcludeDirectories->toPlainText());
247  }
248 
249  QStringList CSettingsSimulatorBasicsComponent::parseDirectories(const QString &rawString) const
250  {
251  const QString raw = rawString.trimmed();
252  if (raw.isEmpty()) { return QStringList(); }
253  QStringList dirs;
254  thread_local const QRegularExpression regExp("\n|\r\n|\r");
255  const QStringList rawLines = raw.split(regExp);
256  for (const QString &l : rawLines)
257  {
258  const QString normalized = CFileUtils::normalizeFilePathToQtStandard(l);
259  if (normalized.isEmpty()) { continue; }
260  dirs.push_back(normalized);
261  }
262  dirs = CFileUtils::removeSubDirectories(dirs);
263  return dirs;
264  }
265 
266  QStringList CSettingsSimulatorBasicsComponent::addDirectory(const QString &directory,
267  const QStringList &existingDirs)
268  {
269  const QString d(CFileUtils::normalizeFilePathToQtStandard(directory));
270  QStringList dirs(existingDirs);
271  if (d.isEmpty()) { return existingDirs; }
272  if (!dirs.contains(d, m_fileCaseSensitivity)) { dirs.push_back(d); }
273  dirs.removeDuplicates();
274  dirs.sort(m_fileCaseSensitivity);
275  return dirs;
276  }
277 
278  QStringList CSettingsSimulatorBasicsComponent::removeDirectory(const QString &directory,
279  const QStringList &existingDirs)
280  {
281  const QString d(CFileUtils::normalizeFilePathToQtStandard(directory));
282  return this->removeDirectories(QStringList({ d }), existingDirs);
283  }
284 
285  QStringList CSettingsSimulatorBasicsComponent::removeDirectories(const QStringList &removeDirectories,
286  const QStringList &existingDirs)
287  {
288  if (existingDirs.isEmpty() || removeDirectories.isEmpty()) { return existingDirs; }
289  const QStringList rDirs = CFileUtils::fixWindowsUncPaths(removeDirectories);
290  const QStringList eDirs = CFileUtils::fixWindowsUncPaths(existingDirs);
291  QStringList dirs;
292  for (const QString &dir : eDirs)
293  {
294  if (rDirs.contains(dir, m_fileCaseSensitivity)) { continue; }
295  dirs.push_back(dir);
296  }
297  dirs.removeDuplicates();
298  dirs.sort(m_fileCaseSensitivity);
299  return dirs;
300  }
301 
302  void CSettingsSimulatorBasicsComponent::displayExcludeDirectoryPatterns(const QStringList &dirs)
303  {
304  QStringList cleanedDirs(dirs);
305  cleanedDirs.removeDuplicates();
306  cleanedDirs.sort(m_fileCaseSensitivity);
307  const QString d = cleanedDirs.join("\n");
308  ui->pte_ExcludeDirectories->setPlainText(d);
309  }
310 
311  void CSettingsSimulatorBasicsComponent::displayModelDirectories(const QStringList &dirs)
312  {
313  QStringList cleanedDirs(dirs);
314  cleanedDirs.removeDuplicates();
315  cleanedDirs.sort(m_fileCaseSensitivity);
316  const QString d = cleanedDirs.join("\n");
317  ui->pte_ModelDirectories->setPlainText(d);
318  this->displayDefaultValuesAsPlaceholder(ui->comp_SimulatorSelector->getValue());
319  }
320 
321  CSpecializedSimulatorSettings CSettingsSimulatorBasicsComponent::getSettings(const CSimulatorInfo &simulator) const
322  {
323  const CSpecializedSimulatorSettings s = m_settings.getSpecializedSettings(simulator);
324  return s;
325  }
326 
327  void CSettingsSimulatorBasicsComponent::displaySettings(const CSimulatorInfo &simulator)
328  {
329  this->displayExcludeDirectoryPatterns(m_settings.getModelExcludeDirectoryPatternsIfNotDefault(simulator));
330  this->displayModelDirectories(m_settings.getModelDirectoriesIfNotDefault(simulator));
331 
332  // ui->le_SimulatorDirectory->setText(m_settings.getSimulatorDirectoryIfNotDefault(simulator));
333  // based on discussion here, always display:
334  // https://discordapp.com/channels/539048679160676382/594962359441948682/700483609361907842
335  const CSimulatorSettings s = m_settings.getSettings(simulator);
336  ui->le_SimulatorDirectory->setText(s.getSimulatorDirectory());
337  }
338 
339  void CSettingsSimulatorBasicsComponent::displayDefaultValuesAsPlaceholder(const CSimulatorInfo &simulator)
340  {
341  const QString simDir = this->getFileBrowserSimulatorDirectory();
342  ui->le_SimulatorDirectory->setPlaceholderText(simDir.isEmpty() ? "Simulator directory" : simDir);
343 
344  // we take the settings and update to latest sim.directory
345  CSpecializedSimulatorSettings settings = m_settings.getSpecializedSettings(simulator);
346  settings.setSimulatorDirectory(simDir);
347 
348  const QStringList m = settings.getModelDirectoriesFromSimulatorDirectoryOrDefault();
349  if (m.isEmpty()) { ui->pte_ModelDirectories->setPlaceholderText("Model directories"); }
350  else
351  {
352  const QString ms = m.join("\n");
353  ui->pte_ModelDirectories->setPlaceholderText(ms);
354  }
355 
356  const QStringList e = settings.getDefaultModelExcludeDirectoryPatterns();
357  if (e.isEmpty()) { ui->pte_ExcludeDirectories->setPlaceholderText("Exclude directories"); }
358  else
359  {
360  const QString es = e.join("\n");
361  ui->pte_ExcludeDirectories->setPlaceholderText(es);
362  }
363  }
364 
365  QString CSettingsSimulatorBasicsComponent::getFileBrowserModelDirectory() const
366  {
367  const CSimulatorInfo simulator(ui->comp_SimulatorSelector->getValue());
368  const QStringList modelDirs(this->parseDirectories(ui->pte_ModelDirectories->toPlainText()));
369  QString md = modelDirs.isEmpty() ? "" : modelDirs.first();
370  if (md.isEmpty()) { md = m_settings.getFirstModelDirectoryOrDefault(simulator); }
371  if (md.isEmpty()) { md = this->getFileBrowserSimulatorDirectory(); }
372  return CFileUtils::normalizeFilePathToQtStandard(md);
373  }
374 
375  QString CSettingsSimulatorBasicsComponent::getFileBrowserSimulatorDirectory() const
376  {
377  const CSimulatorInfo simulator(ui->comp_SimulatorSelector->getValue());
378  QString sd(ui->le_SimulatorDirectory->text().trimmed());
379  if (sd.isEmpty()) { sd = m_settings.getSimulatorDirectoryOrDefault(simulator); }
380  return CFileUtils::normalizeFilePathToQtStandard(sd);
381  }
382 } // namespace swift::gui::components
bool showOverlayHTMLMessage(const QString &htmlMessage, std::chrono::milliseconds timeout=std::chrono::milliseconds(0))
HTML message.
bool showOverlayMessage(const swift::misc::CStatusMessage &message, std::chrono::milliseconds timeout=std::chrono::milliseconds(0))
Show single message.
void showOverlayMessagesOrHTMLMessage(const swift::misc::CStatusMessageList &messages, bool appendOldMessages=false, std::chrono::milliseconds timeout=std::chrono::milliseconds(0))
Show multiple messages or a single message (HTML)
Using this class provides a QFrame with the overlay functionality already integrated.
Driver independent parts of simulator settings, ie those one are also used independent of the driver.
void setSimulator(const swift::misc::simulation::CSimulatorInfo &simulator)
Simulator.
void changed(const swift::misc::simulation::CSimulatorInfo &simulator)
Value has been changed.
static const QString & wizard()
Wizard.
static const QString & guiComponent()
GUI components.
Definition: logcategories.h:94
Class for emitting a log message.
Definition: logmessage.h:27
bool isEmpty() const
Message empty.
Derived & info(const char16_t(&format)[N])
Set the severity to info, providing a format string.
Streamable status message, e.g.
bool isSuccess() const
Operation considered successful.
Status messages, e.g. from Core -> GUI.
bool isSuccess() const
All messages are marked as success.
Simple hardcoded info about the corresponding simulator.
Definition: simulatorinfo.h:41
bool isSingleSimulator() const
Single simulator selected.
QString getFirstModelDirectoryOrDefault(const CSimulatorInfo &simulator) const
First model directoy.
void resetToDefaults(const CSimulatorInfo &simulator)
Reset to defaults.
CSpecializedSimulatorSettings getSpecializedSettings(const CSimulatorInfo &simulator) const
Specialized simulator settings.
QStringList getModelDirectoriesIfNotDefault(const CSimulatorInfo &simulator) const
Model directory or or empty if default.
const QStringList & defaultModelDirectories(const CSimulatorInfo &simulator) const
Default model path per simulator.
CSimulatorSettings getSettings(const CSimulatorInfo &simulator) const
Settings per simulator.
QStringList getModelExcludeDirectoryPatternsIfNotDefault(const CSimulatorInfo &simulator) const
Model exclude patterns or empty if default.
QString getSimulatorDirectoryOrDefault(const CSimulatorInfo &simulator) const
Simulator directory or default model path per simulator.
Settings for simulator Driver independent parts (such as directories), also used in model loaders.
void setModelDirectories(const QStringList &modelDirectories)
Set model directories.
void setSimulatorDirectory(const QString &simulatorDirectory)
Set simulator directory.
void setModelExcludeDirectories(const QStringList &excludeDirectories)
Set exclude directories.
const QString & getSimulatorDirectory() const
Simulator directory.
Allows to have specific utility functions for each simulator.
QStringList getModelDirectoriesFromSimulatorDirectoryOrDefault() const
Model directories, then from simulator directory, then default.
void setSimulatorDirectory(const QString &simDir)
Set simulator directory.
const QStringList & getDefaultModelExcludeDirectoryPatterns() const
Default model exclude patterns.
static const QStringList & defaultModelExcludeDirectoryPatterns(const CSimulatorInfo &simulator)
Default model exclude patterns per simulator.
static const QString & defaultSimulatorDirectory(const CSimulatorInfo &simulator)
Default simulator path per simulator.
High level reusable GUI components.
Definition: aboutdialog.cpp:13
Free functions in swift::misc.