swift
firstmodelsetcomponent.cpp
1 // SPDX-FileCopyrightText: Copyright (C) 2018 swift Project Community / Contributors
2 // SPDX-License-Identifier: GPL-3.0-or-later OR LicenseRef-swift-pilot-client-1
3 
5 
6 #include <QFileDialog>
7 #include <QMessageBox>
8 #include <QPointer>
9 #include <QStringList>
10 
11 #include "ui_firstmodelsetcomponent.h"
12 
13 #include "config/buildconfig.h"
14 #include "core/webdataservices.h"
19 #include "gui/guiapplication.h"
21 #include "misc/directoryutils.h"
22 #include "misc/verify.h"
23 
24 using namespace swift::config;
25 using namespace swift::misc;
26 using namespace swift::misc::simulation;
27 using namespace swift::misc::simulation::settings;
28 
29 namespace swift::gui::components
30 {
31  const QStringList &CFirstModelSetComponent::getLogCategories()
32  {
33  static const QStringList cats { CLogCategories::modelGui() };
34  return cats;
35  }
36 
37  CFirstModelSetComponent::CFirstModelSetComponent(QWidget *parent)
38  : COverlayMessagesFrame(parent), ui(new Ui::CFirstModelSetComponent)
39  {
40  ui->setupUi(this);
41  ui->comp_Distributors->view()->setSelectionMode(QAbstractItemView::MultiSelection);
42  ui->comp_SimulatorSelector->setMode(CSimulatorSelector::RadioButtons);
43  ui->comp_SimulatorSelector->setRememberSelectionAndSetToLastSelection();
44 
45  // we use the powerful component to access own models
46  m_modelsDialog.reset(new CDbOwnModelsDialog(this));
47  m_modelSetDialog.reset(new CDbOwnModelSetDialog(this));
48 
49  this->onSimulatorChanged(ui->comp_SimulatorSelector->getValue());
50 
51  bool s = connect(ui->comp_SimulatorSelector, &CSimulatorSelector::changed, this,
52  &CFirstModelSetComponent::onSimulatorChanged);
53  Q_ASSERT_X(s, Q_FUNC_INFO, "Cannot connect selector signal");
54  connect(&m_simulatorSettings, &CMultiSimulatorSettings::settingsChanged, this,
55  &CFirstModelSetComponent::onSettingsChanged, Qt::QueuedConnection);
56  Q_ASSERT_X(s, Q_FUNC_INFO, "Cannot connect settings signal");
57  connect(m_modelsDialog.data(), &CDbOwnModelsDialog::successfullyLoadedModels, this,
58  &CFirstModelSetComponent::onModelsLoaded, Qt::QueuedConnection);
59  Q_ASSERT_X(s, Q_FUNC_INFO, "Cannot connect models signal");
60 
61  connect(ui->pb_ModelSet, &QPushButton::clicked, this, &CFirstModelSetComponent::openOwnModelSetDialog);
62  connect(ui->pb_Models, &QPushButton::clicked, this, &CFirstModelSetComponent::openOwnModelsDialog);
63  connect(ui->pb_ModelsTriggerReload, &QPushButton::clicked, this, &CFirstModelSetComponent::openOwnModelsDialog);
64  connect(ui->pb_ChangeModelDir, &QPushButton::clicked, this, &CFirstModelSetComponent::changeModelDirectory);
65  connect(ui->pb_ClearModelDir, &QPushButton::clicked, this, &CFirstModelSetComponent::changeModelDirectory);
66  connect(ui->pb_CreateModelSet, &QPushButton::clicked, this, &CFirstModelSetComponent::createModelSet);
67  }
68 
70 
71  void CFirstModelSetComponent::onSimulatorChanged(const CSimulatorInfo &simulator)
72  {
73  if (!simulator.isSingleSimulator())
74  {
77  if (CBuildConfig::isLocalDeveloperDebugBuild())
78  {
79  SWIFT_VERIFY_X(false, Q_FUNC_INFO, "Need single simulator");
80  }
81  CLogMessage(this).error(u"Changing to non-single simulator %1 ignored") << simulator.toQString();
82  return;
83  }
84 
85  Q_ASSERT_X(m_modelsDialog, Q_FUNC_INFO, "No models dialog");
86  m_modelsDialog->setSimulator(simulator);
87 
88  Q_ASSERT_X(m_modelSetDialog, Q_FUNC_INFO, "No model set dialog");
89  m_modelSetDialog->setSimulator(simulator);
90 
91  // distributor component
92  ui->comp_Distributors->filterBySimulator(simulator);
93 
94  const QStringList dirs = m_simulatorSettings.getModelDirectoriesOrDefault(simulator);
95  ui->le_ModelDirectories->setText(dirs.join(", "));
96 
97  // kind of hack, but simplest solution
98  // we us the loader of the components directly,
99  // avoid to fully init a loader logic here
100  static const QString modelsNo("No models so far");
101  const int modelsCount = this->modelLoader()->getCachedModelsCount(simulator);
102  if (modelsCount > 0)
103  {
104  static const QString modelsInfo("%1 included %2 DB key %3");
105  const CAircraftModelList modelsInCache = this->modelLoader()->getCachedModels(simulator);
106  const int modelsIncluded = modelsInCache.countByMode(CAircraftModel::Include);
107  const int modelsDbKey = modelsInCache.countWithValidDbKey(true);
108  ui->le_ModelsInfo->setText(modelsInfo.arg(this->modelLoader()->getCacheCountAndTimestamp(simulator))
109  .arg(modelsIncluded)
110  .arg(modelsDbKey));
111  }
112  else { ui->le_ModelsInfo->setText(modelsNo); }
113 
114  ui->pb_CreateModelSet->setEnabled(modelsCount > 0);
115 
116  static const QString modelsSetNo("Model set is empty");
117  const int modelsSetCount = m_modelSetDialog->modelSetComponent()->getModelSetCount();
118  ui->le_ModelSetInfo->setText(
119  modelsSetCount > 0 ? m_modelSetDialog->modelSetComponent()->getModelCacheCountAndTimestamp() : modelsSetNo);
120  }
121 
122  void CFirstModelSetComponent::onSettingsChanged(const CSimulatorInfo &simulator)
123  {
124  const CSimulatorInfo currentSimulator = ui->comp_SimulatorSelector->getValue();
125  if (simulator != currentSimulator) { return; } // ignore changes not for my selected simulator
126  this->onSimulatorChanged(simulator);
127  }
128 
129  void CFirstModelSetComponent::onModelsLoaded(const CSimulatorInfo &simulator, int count)
130  {
131  Q_UNUSED(count);
132  const CSimulatorInfo currentSimulator = ui->comp_SimulatorSelector->getValue();
133  if (simulator != currentSimulator) { return; } // ignore changes not for my selected simulator
134  this->onSimulatorChanged(simulator);
135  }
136 
137  void CFirstModelSetComponent::triggerSettingsChanged(const CSimulatorInfo &simulator)
138  {
139  if (!sGui || sGui->isShuttingDown()) { return; }
140  QPointer<CFirstModelSetComponent> myself(this);
141  QTimer::singleShot(0, this, [=] {
142  if (!myself || !sGui || sGui->isShuttingDown()) { return; }
143  myself->onSettingsChanged(simulator);
144  });
145  }
146 
147  const CDbOwnModelsComponent *CFirstModelSetComponent::modelsComponent() const
148  {
149  Q_ASSERT_X(m_modelsDialog, Q_FUNC_INFO, "No models dialog");
150  Q_ASSERT_X(m_modelsDialog->modelsComponent(), Q_FUNC_INFO, "No models component");
151  return m_modelsDialog->modelsComponent();
152  }
153 
154  const CDbOwnModelSetComponent *CFirstModelSetComponent::modelSetComponent() const
155  {
156  Q_ASSERT_X(m_modelSetDialog, Q_FUNC_INFO, "No model set dialog");
157  Q_ASSERT_X(m_modelSetDialog->modelSetComponent(), Q_FUNC_INFO, "No model set component");
158  return m_modelSetDialog->modelSetComponent();
159  }
160 
161  IAircraftModelLoader *CFirstModelSetComponent::modelLoader() const
162  {
163  Q_ASSERT_X(m_modelsDialog->modelsComponent()->modelLoader(), Q_FUNC_INFO, "No model loader");
164  return m_modelsDialog->modelsComponent()->modelLoader();
165  }
166 
167  void CFirstModelSetComponent::openOwnModelsDialog()
168  {
169  if (!m_modelsDialog) { return; }
170  if (!sGui || sGui->isShuttingDown() || !sGui->getWebDataServices()) { return; }
171  const bool reload = (QObject::sender() == ui->pb_ModelsTriggerReload);
172 
173  const CSimulatorInfo simulator = ui->comp_SimulatorSelector->getValue();
174  m_modelsDialog->setSimulator(simulator);
175 
176  if (reload)
177  {
179  {
180  const QMessageBox::StandardButton reply = QMessageBox::warning(
181  this->mainWindow(), "DB data", "No DB data, models cannot be consolidated. Load anyway?",
182  QMessageBox::Yes | QMessageBox::No);
183  if (reply != QMessageBox::Yes) { return; }
184  }
185 
186  bool loadOnlyIfNotEmpty = true;
187  if (m_modelsDialog->getOwnModelsCount() > 0)
188  {
189  const QMessageBox::StandardButton reply =
190  QMessageBox::warning(this->mainWindow(), "Model loading",
191  "Reload the models?\nThe existing cache data will we overridden.",
192  QMessageBox::Yes | QMessageBox::No);
193  if (reply == QMessageBox::Yes) { loadOnlyIfNotEmpty = false; }
194  }
195  m_modelsDialog->requestModelsInBackground(simulator, loadOnlyIfNotEmpty);
196  }
197  m_modelsDialog->exec();
198 
199  // force UI update
200  this->triggerSettingsChanged(simulator);
201  }
202 
203  void CFirstModelSetComponent::openOwnModelSetDialog()
204  {
205  const CSimulatorInfo simulator = ui->comp_SimulatorSelector->getValue();
206  m_modelSetDialog->setSimulator(simulator);
207  m_modelSetDialog->enableButtons(false, false);
208  m_modelSetDialog->exec();
209 
210  // force UI update
211  this->triggerSettingsChanged(simulator);
212  }
213 
214  void CFirstModelSetComponent::changeModelDirectory()
215  {
216  using namespace std::chrono_literals;
217 
218  if (!sGui || sGui->isShuttingDown()) { return; }
219  const CSimulatorInfo simulator = ui->comp_SimulatorSelector->getValue();
220  CSpecializedSimulatorSettings settings = m_simulatorSettings.getSpecializedSettings(simulator);
221  const bool clear = (QObject::sender() == ui->pb_ClearModelDir);
222 
223  if (clear) { settings.clearModelDirectories(); }
224  else
225  {
226  const QString dirOld = settings.getFirstModelDirectoryOrDefault();
227  const QString newDir =
228  QFileDialog::getExistingDirectory(this->mainWindow(), tr("Open model directory"), dirOld,
229  QFileDialog::ShowDirsOnly | QFileDialog::DontResolveSymlinks);
230  if (newDir.isEmpty() || CDirectoryUtils::isSameExistingDirectory(dirOld, newDir)) { return; }
231  settings.addModelDirectory(newDir);
232  }
233 
234  const CStatusMessage msg = m_simulatorSettings.setAndSaveSettings(settings, simulator);
235  if (msg.isSuccess()) { this->triggerSettingsChanged(simulator); }
236  else { this->showOverlayMessage(msg, 4s); }
237  }
238 
239  void CFirstModelSetComponent::createModelSet()
240  {
241  using namespace std::chrono_literals;
242  const CSimulatorInfo simulator = ui->comp_SimulatorSelector->getValue();
243  const int modelsCount = this->modelLoader()->getCachedModelsCount(simulator);
244  if (modelsCount < 1)
245  {
246  static const CStatusMessage msg =
247  CStatusMessage(this).validationError(u"No models indexed so far. Try 'reload'!");
248  this->showOverlayMessage(msg, 4s);
249  return;
250  }
251 
252  bool useAllModels = false;
253  if (!ui->comp_Distributors->hasSelectedDistributors())
254  {
255  const QMessageBox::StandardButton reply =
256  QMessageBox::question(this->mainWindow(), "Models", "No distributors selected, use all models?",
257  QMessageBox::Yes | QMessageBox::No);
258  if (reply == QMessageBox::Yes) { useAllModels = true; }
259  else
260  {
261  static const CStatusMessage msg = CStatusMessage(this).validationError(u"No distributors selected");
262  this->showOverlayMessage(msg, 4s);
263  return;
264  }
265  }
266  CAircraftModelList modelsForSet = this->modelLoader()->getCachedModels(simulator);
267  if (!useAllModels)
268  {
269  const CDistributorList distributors = ui->comp_Distributors->getSelectedDistributors();
270  modelsForSet = modelsForSet.findByDistributors(distributors);
271  }
272 
273  if (ui->cb_DbDataOnly->isChecked()) { modelsForSet.removeObjectsWithoutDbKey(); }
274  if (modelsForSet.isEmpty())
275  {
276  this->showOverlayHTMLMessage("Selection yielded no result!");
277  return;
278  }
279 
280  // just in case, paranoia
281  if (!m_modelSetDialog || !m_modelSetDialog->modelSetComponent())
282  {
283  this->showOverlayHTMLMessage("No model set dialog, cannot continue");
284  return;
285  }
286 
287  const int modelsSetCount = m_modelSetDialog->modelSetComponent()->getModelSetCount();
288  if (modelsSetCount > 0)
289  {
290  QMessageBox::StandardButton override = QMessageBox::question(
291  this, "Override", "Override existing model set?", QMessageBox::Yes | QMessageBox::No);
292  if (override != QMessageBox::Yes) { return; }
293  }
294 
295  m_modelSetDialog->modelSetComponent()->setModelSet(modelsForSet, simulator);
296  ui->pb_ModelSet->click();
297  }
298 
299  QWidget *CFirstModelSetComponent::mainWindow()
300  {
301  QWidget *pw = sGui->mainApplicationWidget();
302  return pw ? pw : this;
303  }
304 
306 } // namespace swift::gui::components
bool isShuttingDown() const
Is application shutting down?
CWebDataServices * getWebDataServices() const
Get the web data services.
bool hasDbModelData() const
Are DB model data available?
static QWidget * mainApplicationWidget()
Main application window widget.
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.
Using this class provides a QFrame with the overlay functionality already integrated.
void successfullyLoadedModels(const swift::misc::simulation::CSimulatorInfo &simulator, int count)
Models have been successfully loaded.
void changed(const swift::misc::simulation::CSimulatorInfo &simulator)
Value has been changed.
Class for emitting a log message.
Definition: logmessage.h:27
Derived & validationError(const char16_t(&format)[N])
Set the severity to error, providing a format string, and adding the validation category.
Derived & error(const char16_t(&format)[N])
Set the severity to error, providing a format string.
bool isEmpty() const
Synonym for empty.
Definition: sequence.h:285
Streamable status message, e.g.
bool isSuccess() const
Operation considered successful.
int removeObjectsWithoutDbKey()
Remove objects without key.
int countWithValidDbKey(bool withKey) const
Number of objects with/without key.
QString toQString(bool i18n=false) const
Cast as QString.
Definition: mixinstring.h:76
Value object encapsulating a list of aircraft models.
int countByMode(CAircraftModel::ModelMode mode) const
Count by mode.
CAircraftModelList findByDistributors(const CDistributorList &distributors) const
All models from given distributors.
Value object encapsulating a list of distributors.
Simple hardcoded info about the corresponding simulator.
Definition: simulatorinfo.h:41
bool isSingleSimulator() const
Single simulator selected.
Load the aircraft for a simulator.
CAircraftModelList getCachedModels(const CSimulatorInfo &simulator) const
Look like IMultiSimulatorModelCaches interface.
Definition: modelcaches.h:537
int getCachedModelsCount(const CSimulatorInfo &simulator) const
Look like IMultiSimulatorModelCaches interface.
Definition: modelcaches.h:541
QStringList getModelDirectoriesOrDefault(const CSimulatorInfo &simulator) const
Model directory or default model path per simulator.
Allows to have specific utility functions for each simulator.
bool addModelDirectory(const QString &modelDirectory)
Add (if not exists) model directory.
SWIFT_GUI_EXPORT swift::gui::CGuiApplication * sGui
Single instance of GUI application object.
High level reusable GUI components.
Definition: aboutdialog.cpp:13
Free functions in swift::misc.
auto singleShot(int msec, QObject *target, F &&task)
Starts a single-shot timer which will call a task in the thread of the given object when it times out...
Definition: threadutils.h:30
#define SWIFT_VERIFY_X(COND, WHERE, WHAT)
A weaker kind of assert.
Definition: verify.h:26