SPH
MainWindow.cpp
Go to the documentation of this file.
2 #include "gui/Controller.h"
3 #include "gui/Settings.h"
4 #include "gui/Utils.h"
6 #include "gui/windows/GridPage.h"
8 #include "gui/windows/NodePage.h"
9 #include "gui/windows/PlotView.h"
10 #include "gui/windows/RunPage.h"
12 #include "io/FileSystem.h"
14 #include "post/Plot.h"
15 #include "run/jobs/GeometryJobs.h"
16 #include "run/jobs/IoJobs.h"
17 #include "run/jobs/ParticleJobs.h"
18 #include <fstream>
19 #include <wx/aboutdlg.h>
20 #include <wx/aui/auibook.h>
21 #include <wx/menu.h>
22 #include <wx/msgdlg.h>
23 
25 
26 constexpr int NOTEBOOK_ID = 4257;
27 
28 wxAuiNotebook* findNotebook() {
29  return dynamic_cast<wxAuiNotebook*>(wxWindow::FindWindowById(NOTEBOOK_ID));
30 }
31 
32 
33 static Expected<Path> getRecentSessionCache() {
35  if (home) {
36  return home.value() / Path(".config/opensph/recent.csv");
37  } else {
38  return home;
39  }
40 }
41 
42 static Array<Path> getRecentSessions() {
43  if (Expected<Path> recentCache = getRecentSessionCache()) {
44  try {
45  std::ifstream ifs(recentCache->native());
46  std::string line;
47  if (std::getline(ifs, line)) {
48  Array<std::string> strings = split(line, ',');
49  Array<Path> paths;
50  for (std::string& s : strings) {
51  paths.emplaceBack(s);
52  }
53  return paths;
54  }
55  } catch (const std::exception& UNUSED(e)) {
56  // do nothing
57  }
58  }
59  return {};
60 }
61 
62 constexpr Size MAX_CACHE_SIZE = 8;
63 
64 static void addToRecentSessions(const Path& sessionPath) {
65  SPH_ASSERT(!sessionPath.empty());
66  Array<Path> sessions = getRecentSessions();
67  auto sessionIter = std::find(sessions.begin(), sessions.end(), sessionPath);
68  if (sessionIter != sessions.end()) {
69  // already in the list, remove to move it to the top
70  sessions.remove(sessionIter - sessions.begin());
71  }
72  sessions.insert(0, sessionPath);
73  if (sessions.size() > MAX_CACHE_SIZE) {
74  sessions.pop();
75  }
76 
77  if (Expected<Path> recentCache = getRecentSessionCache()) {
78  try {
79  FileSystem::createDirectory(recentCache->parentPath());
80  std::ofstream ofs(recentCache->native());
81  for (Size i = 0; i < sessions.size(); ++i) {
82  ofs << sessions[i].native();
83  if (i != sessions.size() - 1) {
84  ofs << ",";
85  }
86  }
87  } catch (const std::exception& UNUSED(e)) {
88  }
89  }
90 }
91 
93 private:
94  MainWindow* window;
95 
96 public:
98  : window(window) {}
99 
100  virtual void startRun(SharedPtr<INode> node,
101  const RunSettings& globals,
102  const std::string& name) const override {
103  window->addPage(std::move(node), globals, name);
104  }
105 
106  virtual void markUnsaved(bool UNUSED(addToUndo)) const override {
107  window->markSaved(false);
108  }
109 };
110 
111 
112 MainWindow::MainWindow(const Path& openPath)
113  : wxFrame(nullptr,
114  wxID_ANY,
115 #ifdef SPH_DEBUG
116  std::string("OpenSPH - build: ") + __DATE__ + " (DEBUG)",
117 #else
118  std::string("OpenSPH - build: ") + __DATE__,
119 #endif
120  wxDefaultPosition,
121  wxSize(1024, 768)) {
122 
123  this->Connect(wxEVT_CLOSE_WINDOW, wxCloseEventHandler(MainWindow::onClose));
124 
125  this->Maximize();
126  this->SetAutoLayout(true);
127 
128  // close button does not work in wxGTK
129  notebook = new wxAuiNotebook(this,
130  NOTEBOOK_ID,
131  wxDefaultPosition,
132  wxDefaultSize,
133  wxAUI_NB_DEFAULT_STYLE & ~wxAUI_NB_CLOSE_ON_ACTIVE_TAB);
134  notebook->SetMinSize(wxSize(1024, 768));
135  notebook->Bind(wxEVT_CHAR_HOOK, [this](wxKeyEvent& evt) {
136  const int code = evt.GetKeyCode();
137  if (evt.ControlDown() && code >= int('1') && code <= int('9')) {
138  notebook->SetSelection(code - int('1'));
139  }
140  evt.Skip();
141  });
142 
143  nodePage = new NodeWindow(notebook, makeShared<NodeManagerCallbacks>(this));
144  notebook->AddPage(nodePage, "Unnamed session");
145 
146  wxBoxSizer* sizer = new wxBoxSizer(wxHORIZONTAL);
147  sizer->Add(notebook, 1, wxALL | wxEXPAND);
148 
149  this->SetSizer(sizer);
150 
151  wxMenuBar* bar = new wxMenuBar();
152 
153  wxMenu* projectMenu = this->createProjectMenu();
154  bar->Append(projectMenu, "&Project");
155 
156  runMenu = this->createRunMenu();
157  bar->Append(runMenu, "&Simulation");
158 
159  wxMenu* analysisMenu = this->createAnalysisMenu();
160  bar->Append(analysisMenu, "&Analysis");
161 
162  wxMenu* resultMenu = this->createResultMenu();
163  bar->Append(resultMenu, "&Result");
164 
165  wxMenu* viewMenu = new wxMenu();
166  viewMenu->Append(NodeWindow::ID_PROPERTIES, "&Node properties");
167  viewMenu->Append(NodeWindow::ID_LIST, "&Node list");
168  viewMenu->Bind(wxEVT_COMMAND_MENU_SELECTED, [this](wxCommandEvent& evt) { //
169  nodePage->showPanel(NodeWindow::PanelId(evt.GetId()));
170  });
171  bar->Append(viewMenu, "&Window");
172 
173  wxMenu* helpMenu = new wxMenu();
174  bar->Append(helpMenu, "&Help");
175  helpMenu->Append(0, "&About");
176  helpMenu->Bind(wxEVT_COMMAND_MENU_SELECTED, [](wxCommandEvent& evt) { //
177  switch (evt.GetId()) {
178  case 0: {
179  wxAboutDialogInfo info;
180  info.SetName("OpenSPH");
181 #ifdef SPH_VERSION
182  info.SetVersion(SPH_STR(SPH_VERSION));
183 #else
184  info.SetVersion("unknown");
185 #endif
186 
187  std::string desc;
188 #ifdef SPH_DEBUG
189  desc += "Debug build\n";
190 #else
191  desc += "Release build\n";
192 #endif
193 #ifdef SPH_PROFILE
194  desc += "Profiling enabled\n";
195 #endif
196 #ifdef SPH_USE_TBB
197  desc += "Parallelization: TBB\n";
198 #elif SPH_USE_OPENMP
199  desc += "Parallelization: OpenMP\n";
200 #else
201  desc += "Parallelization: built-in thread pool\n";
202 #endif
203 #ifdef SPH_USE_EIGEN
204  desc += "Eigen: enabled\n";
205 #else
206  desc += "Eigen: disabled\n";
207 #endif
208 #ifdef SPH_USE_VDB
209  desc += "OpenVDB: enabled\n";
210 #else
211  desc += "OpenVDB: disabled\n";
212 #endif
213 #ifdef SPH_USE_CHAISCRIPT
214  desc += "Chaiscript: enabled";
215 #else
216  desc += "Chaiscript: disabled";
217 #endif
218  info.SetDescription(desc);
219  info.SetCopyright("Pavel Sevecek <sevecek@sirrah.troja.mff.cuni.cz>");
220 
221  wxAboutBox(info);
222  break;
223  }
224  default:
226  }
227  });
228 
229  this->SetMenuBar(bar);
230  this->enableMenus(0);
231 
232  notebook->Bind(wxEVT_AUINOTEBOOK_PAGE_CLOSE, [this](wxAuiNotebookEvent& evt) {
233  const int pageId = evt.GetSelection();
234  wxWindow* page = notebook->GetPage(pageId);
235  RunPage* runPage = dynamic_cast<RunPage*>(page);
236  NodeWindow* nodePage = dynamic_cast<NodeWindow*>(page);
237  if (nodePage || (runPage && !closeRun(pageId))) {
238  evt.Veto();
239  }
240  });
241 
242  notebook->Bind(wxEVT_AUINOTEBOOK_PAGE_CHANGED, [this](wxAuiNotebookEvent& evt) {
243  const int pageId = evt.GetSelection();
244  this->enableMenus(pageId);
245  });
246 
247 
248  if (!openPath.empty()) {
250  const std::string ext = openPath.extension().native();
251  if (getIoEnum(ext)) {
252  this->open(openPath, true);
253  } else if (ext == "sph") {
254  this->load(openPath);
255  } else {
256  wxMessageBox("Unrecognized file format", "Error", wxOK);
257  }
258  }
259 
260  Project& project = Project::getInstance();
261  project.getGuiSettings().accessor = [this](GuiSettingsId UNUSED(id)) { markSaved(false); };
262 }
263 
264 void MainWindow::saveAs() {
265  Optional<Path> selectedPath = doSaveFileDialog("Save session", { { "OpenSPH session", "sph" } });
266  if (selectedPath) {
267  this->setProjectPath(selectedPath.value());
268  this->save();
269  }
270 }
271 
272 void MainWindow::save() {
273  SPH_ASSERT(!projectPath.empty());
274  BusyCursor wait(this);
275 
276  Config config;
277  // get project data (gui, palettes, ...)
278  Project& project = Project::getInstance();
279  project.save(config);
280 
281  // get node data
282  nodePage->save(config);
283 
284  config.save(projectPath);
285 
286  this->markSaved(true);
287  addToRecentSessions(FileSystem::getAbsolutePath(projectPath));
288 }
289 
290 void MainWindow::open(const Path& openPath, const bool setDefaults) {
291  BusyCursor wait(this);
292 
293  if (setDefaults) {
294  // if loading a file specified as parameter, modify defaults if its SPH
296  BinaryInput input;
297  Expected<BinaryInput::Info> info = input.getInfo(openPath);
298  const bool isSphSim = info && info->runType == RunTypeEnum::SPH;
299  const bool isMiluphSim = openPath.extension() == Path("h5");
300  if (isSphSim || isMiluphSim) {
302  }
303  }
304  AutoPtr<Controller> controller = makeAuto<Controller>(notebook);
305  controller->open(openPath);
306 
307  const Size index = notebook->GetPageCount();
308  RunPage* page = &*controller->getPage();
309  SPH_ASSERT(page != nullptr);
310 
311  RunData data;
312  data.controller = std::move(controller);
313  data.isRun = false;
314  runs.insert(page, std::move(data));
315 
316  const Path displayedPath = openPath.parentPath().fileName() / openPath.fileName();
317  notebook->AddPage(page, displayedPath.native());
318  notebook->SetSelection(index);
319 
320  this->enableMenus(index);
321 }
322 
323 void MainWindow::load(const Path& openPath) {
324  BusyCursor wait(this);
325 
326  Path pathToLoad;
327  if (openPath.empty()) {
328  Optional<Path> selectedPath = doOpenFileDialog("Open session", { { "OpenSPH session", "sph" } });
329  if (selectedPath && FileSystem::pathExists(selectedPath.value())) {
330  pathToLoad = selectedPath.value();
331  } else {
332  return;
333  }
334  } else {
335  pathToLoad = openPath;
336  }
337 
338  if (!FileSystem::pathExists(pathToLoad)) {
339  wxMessageBox("File '" + pathToLoad.native() + "' does not exist.");
340  return;
341  }
342 
343  const bool removed = this->removeAll();
344  if (!removed) {
345  return;
346  }
347 
348  Config config;
349  try {
350  config.load(pathToLoad);
351  } catch (const Exception& e) {
352  wxMessageBox(std::string("Cannot load: ") + e.what(), "Error", wxOK);
353  return;
354  }
355 
356  try {
357  Project& project = Project::getInstance();
358  project.load(config);
359  nodePage->load(config);
360  } catch (const Exception& e) {
361  wxMessageBox(std::string("Cannot load: ") + e.what(), "Error", wxOK);
362  return;
363  }
364 
365  this->setProjectPath(pathToLoad);
366  addToRecentSessions(FileSystem::getAbsolutePath(pathToLoad));
367 }
368 
369 void MainWindow::setProjectPath(const Path& newPath) {
370  projectPath = newPath;
371  const int pageIndex = notebook->GetPageIndex(nodePage);
372  if (!projectPath.empty()) {
373  notebook->SetPageText(
374  pageIndex, "Session '" + projectPath.fileName().removeExtension().native() + "'");
375  } else {
376  notebook->SetPageText(pageIndex, "Unnamed session");
377  }
378 }
379 
380 void MainWindow::markSaved(const bool saved) {
382  if (savedFlag == saved) {
383  return;
384  }
385 
386  savedFlag = saved;
387  if (saved) {
388  this->setProjectPath(projectPath); // remove the '*' mark
389  } else {
390  const int pageIndex = notebook->GetPageIndex(nodePage);
391  notebook->SetPageText(pageIndex, notebook->GetPageText(pageIndex) + " *");
392  }
393 }
394 
395 wxMenu* MainWindow::createProjectMenu() {
396  wxMenu* projectMenu = new wxMenu();
397  projectMenu->Append(0, "&New session\tCtrl+N");
398  projectMenu->Append(1, "&Save session\tCtrl+S");
399  projectMenu->Append(2, "&Save session as");
400  projectMenu->Append(3, "&Open session\tCtrl+Shift+O");
401 
402  wxMenu* recentMenu = new wxMenu();
403  projectMenu->AppendSubMenu(recentMenu, "&Recent");
404  projectMenu->Append(4, "&Visualization settings...");
405  projectMenu->Append(5, "&Shared properties...");
406  projectMenu->Append(6, "&Batch run\tCtrl+B");
407  projectMenu->Append(7, "&Quit");
408 
409  SharedPtr<Array<Path>> recentSessions = makeShared<Array<Path>>();
410  *recentSessions = getRecentSessions();
411  for (Size i = 0; i < recentSessions->size(); ++i) {
412  recentMenu->Append(i, (*recentSessions)[i].native());
413  }
414 
415  // wx handlers need to be copyable, we thus cannot capture Array
416  recentMenu->Bind(wxEVT_COMMAND_MENU_SELECTED,
417  [this, recentSessions](wxCommandEvent& evt) { this->load((*recentSessions)[evt.GetId()]); });
418 
419  projectMenu->Bind(wxEVT_COMMAND_MENU_SELECTED, [this](wxCommandEvent& evt) { //
420  switch (evt.GetId()) {
421  case 0: {
422  // end running simulations
423  if (!this->removeAll()) {
424  break;
425  }
426  // ask user if unsaved
427  if (checkUnsavedSession() == wxCANCEL) {
428  break;
429  }
430  auto nameMgr = nodePage->makeUniqueNameManager();
431  SessionDialog* dialog = new SessionDialog(this, nameMgr);
432  if (dialog->ShowModal() == wxID_OK) {
433  this->setProjectPath(Path());
434  nodePage->reset();
435  auto node = dialog->selectedPreset();
436  if (node) {
437  nodePage->addNodes(*node);
438  }
439  }
440  dialog->Destroy();
441  break;
442  }
443  case 1: {
444  if (projectPath.empty()) {
445  this->saveAs();
446  } else {
447  this->save();
448  }
449  break;
450  }
451  case 2:
452  this->saveAs();
453  break;
454  case 3:
455  this->load();
456  break;
457  case 4: {
458  GuiSettingsDialog* dialog = new GuiSettingsDialog(this);
459  dialog->ShowModal();
460  break;
461  }
462  case 5:
463  notebook->SetSelection(0);
464  nodePage->showGlobals();
465  break;
466  case 6:
467  nodePage->showBatchDialog();
468  break;
469  case 7:
470  this->Close();
471  break;
472  default:
474  }
475  });
476  return projectMenu;
477 }
478 
479 wxMenu* MainWindow::createResultMenu() {
480  wxMenu* fileMenu = new wxMenu();
481  fileMenu->Append(0, "&Open\tCtrl+O");
482  fileMenu->Append(1, "&Close current\tCtrl+W");
483 
484  fileMenu->Bind(wxEVT_COMMAND_MENU_SELECTED, [this](wxCommandEvent& evt) { //
485  switch (evt.GetId()) {
486  case 0: {
487  Optional<Path> path = doOpenFileDialog("Open file",
488  { { "SPH state file", "ssf" },
489  { "SPH compressed file", "scf" },
490  { "miluphcuda output files", "h5" },
491  { "Text .tab files", "tab" } });
492  if (path) {
493  this->open(path.value(), false);
494  }
495  break;
496  }
497  case 1: {
498  wxWindow* page = notebook->GetCurrentPage();
499  if (dynamic_cast<NodeWindow*>(page)) {
500  // node page should not be closeable
501  break;
502  }
503 
504  closeRun(notebook->GetPageIndex(page));
505  break;
506  }
507  default:
509  }
510 
511  });
512  return fileMenu;
513 }
514 
515 enum RunMenuId {
525 };
526 
527 wxMenu* MainWindow::createRunMenu() {
528  wxMenu* runMenu = new wxMenu();
529  runMenu->Append(RUN_START, "S&tart run\tCtrl+R");
530  runMenu->Append(RUN_START_SCRIPT, "Start script");
531  runMenu->Append(RUN_RESTART, "&Restart");
532  runMenu->Append(RUN_PAUSE, "&Pause");
533  runMenu->Append(RUN_STOP, "St&op");
534  runMenu->Append(RUN_SAVE_STATE, "&Save current state");
535  runMenu->Append(RUN_CREATE_CAMERA, "Make camera node");
536  runMenu->Append(RUN_CLOSE_CURRENT, "&Close current\tCtrl+W");
537  runMenu->Append(RUN_CLOSE_ALL, "Close all");
538 
539  runMenu->Bind(wxEVT_COMMAND_MENU_SELECTED, [this, runMenu](wxCommandEvent& evt) { //
540  // options not related to a particular controller
541  if (evt.GetId() == RUN_START) {
542  nodePage->selectRun();
543  return;
544  } else if (evt.GetId() == RUN_START_SCRIPT) {
545 #ifdef SPH_USE_CHAISCRIPT
546  Optional<Path> scriptPath = doOpenFileDialog("Chai script", { { "Chai script", "chai" } });
547  if (scriptPath) {
548  nodePage->startScript(scriptPath.value());
549  }
550 #else
551  wxMessageBox("The code needs to be compiled with ChaiScript support.", "No ChaiScript", wxOK);
552 #endif
553  return;
554  }
555 
556  RunPage* page = dynamic_cast<RunPage*>(notebook->GetCurrentPage());
557  if (!page) {
558  return;
559  }
560  RawPtr<Controller> controller = runs[page].controller.get();
561 
562  switch (evt.GetId()) {
563  case RUN_RESTART:
564  controller->stop(true);
565  controller->restart();
566  break;
567  case RUN_PAUSE: {
568  RunStatus status = controller->getStatus();
569  wxMenuItem* item = runMenu->FindItem(RUN_PAUSE);
570  if (status == RunStatus::PAUSED) {
571  controller->restart();
572  item->SetItemLabel("&Pause");
573  } else {
574  controller->pause();
575  item->SetItemLabel("Un&pause");
576  }
577  break;
578  }
579  case RUN_STOP:
580  controller->stop();
581  break;
582  case RUN_SAVE_STATE: {
583  Optional<Path> path = doSaveFileDialog("Save state file", getOutputFormats());
584  if (!path) {
585  return;
586  }
587  controller->saveState(path.value());
588  break;
589  }
590  case RUN_CREATE_CAMERA: {
591  AutoPtr<ICamera> camera = controller->getCurrentCamera();
592  auto nameMgr = nodePage->makeUniqueNameManager();
593  AutoPtr<OrthoCameraJob> job = makeAuto<OrthoCameraJob>(nameMgr.getName("hand-held camera"));
594  VirtualSettings settings = job->getSettings();
595  AffineMatrix frame = camera->getFrame();
596  const Vector posKm = frame.translation() * 1.e-3_f;
597 
598  settings.set(GuiSettingsId::CAMERA_POSITION, posKm);
599  settings.set(GuiSettingsId::CAMERA_UP, frame.row(1));
600  settings.set(GuiSettingsId::CAMERA_TARGET, posKm + frame.row(2));
601  const Optional<float> wtp = camera->getWorldToPixel();
602  if (wtp) {
603  settings.set(GuiSettingsId::CAMERA_ORTHO_FOV, 1.e-3_f * camera->getSize().y / wtp.value());
604  }
605  nodePage->createNode(std::move(job));
606  notebook->SetSelection(notebook->GetPageIndex(nodePage));
607  break;
608  }
609  case RUN_CLOSE_CURRENT:
610  closeRun(notebook->GetPageIndex(page));
611  break;
612  case RUN_CLOSE_ALL:
613  this->removeAll();
614  break;
615  default:
617  }
618  });
619 
620  return runMenu;
621 }
622 
623 
624 wxMenu* MainWindow::createAnalysisMenu() {
625  wxMenu* analysisMenu = new wxMenu();
626  analysisMenu->Append(0, "Current SFD");
627  analysisMenu->Append(1, "Predicted SFD");
628  analysisMenu->Append(2, "Velocity histogram");
629  analysisMenu->Append(3, "Density profile");
630  analysisMenu->Append(4, "Energy profile");
631  analysisMenu->Append(5, "Pressure profile");
632  analysisMenu->Append(6, "Fragment parameters");
633  analysisMenu->Bind(wxEVT_COMMAND_MENU_SELECTED, [this](wxCommandEvent& evt) { //
634  BusyCursor wait(this);
635  RunPage* page = dynamic_cast<RunPage*>(notebook->GetCurrentPage());
636  if (!page) {
637  return;
638  }
639  RawPtr<Controller> controller = runs[page].controller.get();
640 
641  if (evt.GetId() == 6) { // not a plot, requires special handling
642  GridPage* gridPage = new GridPage(notebook, wxSize(800, 600), controller->getStorage());
643 
644  const Size index = notebook->GetPageCount();
645  notebook->AddPage(gridPage, "Fragments");
646  notebook->SetSelection(index);
647  return;
648  }
649 
650  // plot options below
651  LockingPtr<IPlot> plot;
652  switch (evt.GetId()) {
653  case 0:
654  case 1: {
655  Post::ComponentFlag flag =
657 
658  Array<AutoPtr<IPlot>> multiplot;
659  multiplot.emplaceBack(makeAuto<SfdPlot>(flag, 0._f));
660  Project& project = Project::getInstance();
661  const std::string overplotSfd =
662  project.getGuiSettings().get<std::string>(GuiSettingsId::PLOT_OVERPLOT_SFD);
663  if (!overplotSfd.empty()) {
664  multiplot.emplaceBack(getDataPlot(Path(overplotSfd), "overplot"));
665  }
666  plot = makeLocking<MultiPlot>(std::move(multiplot));
667  break;
668  }
669  case 2:
670  plot = makeLocking<HistogramPlot>(Post::HistogramId::VELOCITIES, NOTHING, 0._f, "Velocity");
671  break;
672  case 3:
673  plot = makeLocking<RadialDistributionPlot>(QuantityId::DENSITY);
674  break;
675  case 4:
676  plot = makeLocking<RadialDistributionPlot>(QuantityId::ENERGY);
677  break;
678  case 5:
679  if (!controller->getStorage().has(QuantityId::PRESSURE)) {
680  wxMessageBox("No pressure data", "Error", wxOK);
681  return;
682  }
683  plot = makeLocking<RadialDistributionPlot>(QuantityId::PRESSURE);
684  break;
685  default:
687  }
688  Statistics stats;
689  stats.set(StatisticsId::RUN_TIME, 0._f);
690  plot->onTimeStep(controller->getStorage(), stats);
691 
692  PlotPage* plotPage = new PlotPage(notebook, wxSize(800, 600), wxSize(25, 25), plot);
693 
694  const Size index = notebook->GetPageCount();
695  // needs to be called before, AddPage calls onPaint, which locks the mutex
696  const std::string caption = plot->getCaption();
697  notebook->AddPage(plotPage, caption);
698  notebook->SetSelection(index);
699 
700  });
701  return analysisMenu;
702 }
703 
704 void MainWindow::addPage(SharedPtr<INode> node, const RunSettings& globals, const std::string pageName) {
705  AutoPtr<Controller> controller = makeAuto<Controller>(notebook);
706  controller->start(std::move(node), globals);
707 
708  RunPage* page = &*controller->getPage();
709  RunData data;
710  data.controller = std::move(controller);
711  data.isRun = true;
712  runs.insert(page, std::move(data));
713 
714  const Size index = notebook->GetPageCount();
715  notebook->AddPage(page, pageName);
716  notebook->SetSelection(index);
717 
718  this->enableMenus(index);
719 }
720 
721 bool MainWindow::removeAll() {
722  for (int i = notebook->GetPageCount() - 1; i >= 0; --i) {
723  if (dynamic_cast<RunPage*>(notebook->GetPage(i)) && !closeRun(i)) {
724  return false;
725  }
726  }
727  return true;
728 }
729 
730 void MainWindow::onClose(wxCloseEvent& evt) {
731  if (checkUnsavedSession() == wxCANCEL) {
732  evt.Veto();
733  return;
734  }
735  this->Destroy();
736 }
737 
738 void MainWindow::enableMenus(const Size id) {
740 
741  wxMenuBar* bar = this->GetMenuBar();
742  RunPage* page = dynamic_cast<RunPage*>(notebook->GetPage(id));
743  if (page == nullptr) {
744  enableRunMenu(false, false);
745  // disable analysis
746  bar->EnableTop(2, false);
747  return;
748  }
749  SPH_ASSERT(runs.contains(page));
750 
751  const bool enableControls = runs[page].isRun;
752  enableRunMenu(enableControls, true);
753  // enable/disable analysis
754  bar->EnableTop(2, true);
755 }
756 
757 void MainWindow::enableRunMenu(const bool enableControls, const bool enableCamera) {
758  wxMenuItemList& list = runMenu->GetMenuItems();
759  for (Size i = 0; i < list.size(); ++i) {
760  if (i == RUN_START || i == RUN_START_SCRIPT) {
761  // always enabled
762  list[i]->Enable(true);
763  continue;
764  }
765  if (i == RUN_CREATE_CAMERA) {
766  list[i]->Enable(enableCamera);
767  } else {
768  list[i]->Enable(enableControls);
769  }
770  }
771 }
772 
773 bool MainWindow::closeRun(const Size id) {
774  wxWindow* page = notebook->GetPage(id);
775  if (RunPage* runPage = dynamic_cast<RunPage*>(page)) {
776  if (!runPage->close()) {
777  // veto'd
778  return false;
779  }
780 
781  // destroy the associated controller
782  runs.remove(runPage);
783  }
784  notebook->DeletePage(id);
785  return true;
786 }
787 
788 int MainWindow::checkUnsavedSession() {
789  if (savedFlag) {
790  return true;
791  }
792  const int retval = wxMessageBox("Save unsaved changes", "Save?", wxYES_NO | wxCANCEL | wxCENTRE);
793  if (retval == wxYES) {
794  if (projectPath.empty()) {
795  this->saveAs();
796  } else {
797  this->save();
798  }
799  }
800  return retval;
801 }
#define SPH_ASSERT(x,...)
Definition: Assert.h:94
#define NOT_IMPLEMENTED
Helper macro marking missing implementation.
Definition: Assert.h:100
NAMESPACE_SPH_BEGIN
Definition: BarnesHut.cpp:13
@ MAIN_THREAD
Function can only be executed from main thread.
#define CHECK_FUNCTION(flags)
Definition: CheckFunction.h:40
RunStatus
Status of the code.
Definition: Controller.h:33
@ PAUSED
Run is paused, can be continued or stopped.
uint32_t Size
Integral type used to index arrays (by default).
Definition: Globals.h:16
Helper objects allowing to iterate in reverse, iterate over multiple containeres, etc.
RunMenuId
Definition: MainWindow.cpp:515
@ RUN_SAVE_STATE
Definition: MainWindow.cpp:521
@ RUN_RESTART
Definition: MainWindow.cpp:518
@ RUN_START_SCRIPT
Definition: MainWindow.cpp:517
@ RUN_CLOSE_CURRENT
Definition: MainWindow.cpp:523
@ RUN_CLOSE_ALL
Definition: MainWindow.cpp:524
@ RUN_PAUSE
Definition: MainWindow.cpp:519
@ RUN_CREATE_CAMERA
Definition: MainWindow.cpp:522
@ RUN_START
Definition: MainWindow.cpp:516
@ RUN_STOP
Definition: MainWindow.cpp:520
constexpr NAMESPACE_SPH_BEGIN int NOTEBOOK_ID
Definition: MainWindow.cpp:26
wxAuiNotebook * findNotebook()
Definition: MainWindow.cpp:28
constexpr Size MAX_CACHE_SIZE
Definition: MainWindow.cpp:62
#define UNUSED(x)
Definition: Object.h:37
#define NAMESPACE_SPH_END
Definition: Object.h:12
const NothingType NOTHING
Definition: Optional.h:16
@ SPH
Main SPH simulation.
Drawing of plots.
Drawing quantity values as functions of time or spatial coordinates.
AutoPtr< IPlot > getDataPlot(const Path &path, const std::string &name)
Definition: Plots.cpp:275
@ PRESSURE
Pressure, affected by yielding and fragmentation model, always a scalar quantity.
@ ENERGY
Specific internal energy, always a scalar quantity.
@ DENSITY
Density, always a scalar quantity.
@ RUN_TIME
Current time of the simulation in code units. Does not necessarily have to be 0 when run starts.
Array< std::string > split(const std::string &s, const char delimiter)
Splits a string into an array of string using given delimiter.
Optional< Path > doSaveFileDialog(const std::string &title, Array< FileFormat > &&formats)
Definition: Utils.cpp:56
Optional< Path > doOpenFileDialog(const std::string &title, Array< FileFormat > &&formats)
Definition: Utils.cpp:45
Random utility functions for drawing stuff to DC.
Array< IVirtualEntry::FileFormat > getOutputFormats()
Convenience function, returning the list of output file formats defined by IoEnum.
INLINE Vector translation() const
Definition: AffineMatrix.h:49
INLINE Vector row(const Size idx) const
Definition: AffineMatrix.h:44
Generic dynamically allocated resizable storage.
Definition: Array.h:43
INLINE Iterator< StorageType > end() noexcept
Definition: Array.h:462
StorageType & emplaceBack(TArgs &&... args)
Constructs a new element at the end of the array in place, using the provided arguments.
Definition: Array.h:332
void remove(const TCounter idx)
Removes an element with given index from the array.
Definition: Array.h:383
void insert(const TCounter position, U &&value)
Inserts a new element to given position in the array.
Definition: Array.h:345
INLINE T pop()
Removes the last element from the array and return its value.
Definition: Array.h:375
INLINE TCounter size() const noexcept
Definition: Array.h:193
INLINE Iterator< StorageType > begin() noexcept
Definition: Array.h:450
Input for the binary file, generated by BinaryOutput.
Definition: Output.h:352
Expected< Info > getInfo(const Path &path) const
Opens the file and reads header info without reading the rest of the file.
Definition: Output.cpp:814
Provides functionality for reading and writing configuration files.
Definition: Config.h:272
void save(const Path &path)
Serializes all nodes in the config into a file.
Definition: Config.cpp:126
void load(const Path &path)
Reads content of given file and deserializes the config from the loaded string.
Definition: Config.cpp:131
RawPtr< RunPage > getPage() const
Definition: Controller.cpp:43
AutoPtr< ICamera > getCurrentCamera() const
Returns the camera currently used for the rendering.
Definition: Controller.cpp:437
void saveState(const Path &path)
Saves the state of the current run to the disk.
Definition: Controller.cpp:150
void restart()
Starts the simulation with current setup.
Definition: Controller.cpp:105
RunStatus getStatus() const
Returns the current status of the run.
Definition: Controller.cpp:146
void open(const Path &path, const bool sequence=false)
Opens a simulation snapshot from given file.
Definition: Controller.cpp:92
void stop(const bool waitForFinish=false)
Stops the current simulation.
Definition: Controller.cpp:132
void pause()
Pause the current simulation.
Definition: Controller.cpp:127
void start(SharedPtr< INode > run, const RunSettings &globals)
Sets up and starts a new simulation.
Definition: Controller.cpp:74
const Storage & getStorage() const
Definition: Controller.cpp:574
Generic exception.
Definition: Exceptions.h:10
virtual const char * what() const noexcept
Definition: Exceptions.h:18
Wrapper of type that either contains a value of given type, or an error message.
Definition: Expected.h:25
Type & value()
Returns the reference to expected value.
Definition: Expected.h:69
INLINE bool contains(const TKey &key) const
Returns true if the map contains element of given key.
Definition: FlatMap.h:140
INLINE void remove(const TKey &key)
Removes element with given key from the map.
Definition: FlatMap.h:89
INLINE TValue & insert(const TKey &key, const TValue &value)
Adds a new element into the map or sets new value of element with the same key.
Definition: FlatMap.h:65
INLINE TValue get(const GuiSettingsId id) const
Definition: Settings.h:245
INLINE GuiSettings & set(const GuiSettingsId id, const TValue &value)
Definition: Settings.h:250
Function< void(GuiSettingsId id)> accessor
Definition: Settings.h:242
virtual Pixel getSize() const =0
Returns the current resolution of the camera.
virtual AffineMatrix getFrame() const =0
Returns the transformation matrix converting camera space to world space.
virtual Optional< float > getWorldToPixel() const =0
Returns the world-to-pixel ratio.
MainWindow(const Path &openPath=Path())
Definition: MainWindow.cpp:112
virtual void startRun(SharedPtr< INode > node, const RunSettings &globals, const std::string &name) const override
Definition: MainWindow.cpp:100
NodeManagerCallbacks(MainWindow *window)
Definition: MainWindow.cpp:97
virtual void markUnsaved(bool UNUSED(addToUndo)) const override
Definition: MainWindow.cpp:106
@ ID_PROPERTIES
Definition: NodePage.h:219
SharedPtr< JobNode > createNode(AutoPtr< IJob > &&job)
Definition: NodePage.cpp:1799
void load(Config &config)
Definition: NodePage.cpp:1785
UniqueNameManager makeUniqueNameManager() const
Definition: NodePage.cpp:1853
void addNodes(JobNode &node)
Definition: NodePage.cpp:1794
void showPanel(const PanelId id)
Definition: NodePage.cpp:1738
void save(Config &config)
Definition: NodePage.cpp:1778
void reset()
Definition: NodePage.cpp:1773
void startScript(const Path &file)
Definition: NodePage.cpp:1769
void showBatchDialog()
Definition: NodePage.cpp:1761
void showGlobals()
Definition: NodePage.cpp:1756
Wrapper of type value of which may or may not be present.
Definition: Optional.h:23
INLINE Type & value()
Returns the reference to the stored value.
Definition: Optional.h:172
Object representing a path on a filesystem.
Definition: Path.h:17
Path & removeExtension()
Removes the extension from the path.
Definition: Path.cpp:100
std::string native() const
Returns the native version of the path.
Definition: Path.cpp:71
bool empty() const
Checks if the path is empty.
Definition: Path.cpp:10
Path fileName() const
Returns the filename of the path.
Definition: Path.cpp:47
Path parentPath() const
Returns the parent directory. If the path is empty or root, return empty path.
Definition: Path.cpp:35
Path extension() const
Returns the extension of the filename.
Definition: Path.cpp:59
void load(Config &config)
Definition: Project.cpp:40
GuiSettings & getGuiSettings()
Definition: Project.h:51
static Project & getInstance()
Definition: Project.h:25
void save(Config &config)
Definition: Project.cpp:35
Main frame of the application.
Definition: RunPage.h:44
bool close()
Definition: RunPage.cpp:963
SharedPtr< JobNode > selectedPreset() const
Definition: SessionDialog.h:15
Object holding various statistics about current run.
Definition: Statistics.h:22
Statistics & set(const StatisticsId idx, TValue &&value)
Sets new values of a statistic.
Definition: Statistics.h:52
bool has(const QuantityId key) const
Checks if the storage contains quantity with given key.
Definition: Storage.cpp:130
Holds a map of virtual entries, associated with a unique name.
void set(const std::string &key, const IVirtualEntry::Value &value)
Modifies an existing entry in the settings.
Optional< IoEnum > getIoEnum(const std::string &ext)
Returns the file type from file extension.
Definition: Settings.cpp:367
GuiSettingsId
Definition: Settings.h:91
@ PARTICLE_RADIUS
Displayed radius of particle in units of smoothing length.
@ CAMERA_ORTHO_FOV
View field of view (zoom). Special value 0 means the field of view is computed from the bounding box.
bool pathExists(const Path &path)
Checks if a file or directory exists (or more precisely, if a file or directory is accessible).
Definition: FileSystem.cpp:21
Outcome createDirectory(const Path &path, const Flags< CreateDirectoryFlag > flags=CreateDirectoryFlag::ALLOW_EXISTING)
Creates a directory with given path. Creates all parent directories as well.
Definition: FileSystem.cpp:119
Expected< Path > getHomeDirectory()
Returns the home directory of the current user.
Definition: FileSystem.cpp:39
Path getAbsolutePath(const Path &relativePath)
Returns the absolute path to the file.
Definition: FileSystem.cpp:48
ComponentFlag
Definition: Analysis.h:39
@ OVERLAP
Specifies that overlapping particles belong into the same component.
@ VELOCITIES
Particle velocities.
Overload of std::swap for Sph::Array.
Definition: Array.h:578