diff --git a/src/gui/app.cc b/src/gui/app.cc index 734909a344..309ceed6b2 100644 --- a/src/gui/app.cc +++ b/src/gui/app.cc @@ -2,7 +2,7 @@ // This file is part of Gambit // Copyright (c) 1994-2026, The Gambit Project (https://www.gambit-project.org) // -// FILE: src/gui/gambit.cc +// FILE: src/gui/app.cc // Implementation of main wxApp class // // This program is free software; you can redistribute it and/or modify @@ -20,8 +20,6 @@ // Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. // -#include - #include #ifndef WX_PRECOMP #include @@ -181,48 +179,35 @@ class FileErrorDialog final : public wxDialog { AppLoadResult Application::LoadFile(const wxString &p_filename, wxWindow *p_parent) { - std::ifstream infile(p_filename.mb_str()); - if (!infile.good()) { + const auto [result, doc] = GameDocument::Load(p_filename); + + switch (result) { + case GameDocument::LoadResult::OpenFailed: FileErrorDialog(p_parent, _("Unable to open file"), _("Gambit could not open this file for reading."), p_filename) .ShowModal(); return AppLoadResult::OpenFailed; - } - auto *doc = new GameDocument(NewTree()); - if (doc->LoadWorkspace(p_filename)) { - doc->SetFilename(p_filename); - m_fileHistory.AddFileToHistory(p_filename); - m_fileHistory.Save(*wxConfigBase::Get()); - (void)new GameFrame(nullptr, doc); - return AppLoadResult::Success; - } - delete doc; - - try { - const Game game = ReadGame(infile); - if (game->IsAgg()) { - FileErrorDialog(p_parent, _("Unsupported game representation"), - _("Action graph games are not currently supported by the " - "graphical interface."), - p_filename) - .ShowModal(); - return AppLoadResult::UnsupportedRepresentation; - } + case GameDocument::LoadResult::ParseFailed: + FileErrorDialog(p_parent, _("Unable to read file"), + _("File is not in a format Gambit recognizes."), p_filename) + .ShowModal(); + return AppLoadResult::ParseFailed; + case GameDocument::LoadResult::UnsupportedRepresentation: + FileErrorDialog(p_parent, _("Unsupported game representation"), + _("Action graph games are not currently supported by the " + "graphical interface."), + p_filename) + .ShowModal(); + return AppLoadResult::UnsupportedRepresentation; + + case GameDocument::LoadResult::Success: m_fileHistory.AddFileToHistory(p_filename); m_fileHistory.Save(*wxConfigBase::Get()); - doc = new GameDocument(game); - doc->SetFilename(p_filename); (void)new GameFrame(nullptr, doc); return AppLoadResult::Success; } - catch (InvalidFileException &) { - FileErrorDialog(p_parent, _("Unable to read file"), - _("File is not in a format Gambit recognizes."), p_filename) - .ShowModal(); - return AppLoadResult::ParseFailed; - } } void Application::SetCurrentDir(const wxString &p_dir) diff --git a/src/gui/dlefglogit.cc b/src/gui/dlefglogit.cc index 6561719063..0bb3484dff 100644 --- a/src/gui/dlefglogit.cc +++ b/src/gui/dlefglogit.cc @@ -46,14 +46,14 @@ namespace Gambit::GUI { //! isn't resized/repainted on every single profile received. //! class LogitBehavTable final : public wxGridTableBase { - GameDocument *m_doc; + std::shared_ptr m_doc; Array m_lambdas; Array>> m_profiles; int m_numCols{0}; int m_numRows{0}; public: - explicit LogitBehavTable(GameDocument *p_doc) : m_doc(p_doc) {} + explicit LogitBehavTable(const std::shared_ptr &p_doc) : m_doc(p_doc) {} int GetNumberRows() override { return m_numRows; } int GetNumberCols() override { return m_numCols; } @@ -172,7 +172,7 @@ class LogitBehavGrid final : public wxGrid { //@} public: - LogitBehavGrid(wxWindow *p_parent, GameDocument *p_doc) + LogitBehavGrid(wxWindow *p_parent, const std::shared_ptr &p_doc) : wxGrid(p_parent, wxID_ANY), m_gridTable(new LogitBehavTable(p_doc)) { SetTable(m_gridTable, true); @@ -258,7 +258,7 @@ class LogitBehavThreadRunner final : public wxThread { void RequestCancel() { m_cancel.RequestCancel(); } }; -LogitBehavDialog::LogitBehavDialog(wxWindow *p_parent, GameDocument *p_doc) +LogitBehavDialog::LogitBehavDialog(wxWindow *p_parent, const std::shared_ptr &p_doc) : wxDialog(p_parent, wxID_ANY, wxT("Compute quantal response equilibria"), wxDefaultPosition), m_doc(p_doc), m_behavList(new LogitBehavGrid(this, m_doc)) { diff --git a/src/gui/dlefglogit.h b/src/gui/dlefglogit.h index 43844818e2..727463dedb 100644 --- a/src/gui/dlefglogit.h +++ b/src/gui/dlefglogit.h @@ -30,7 +30,7 @@ class LogitBehavGrid; class LogitBehavThreadRunner; class LogitBehavDialog final : public wxDialog { - GameDocument *m_doc; + std::shared_ptr m_doc; std::unique_ptr m_runner; LogitBehavGrid *m_behavList; wxStaticText *m_statusText; @@ -47,7 +47,7 @@ class LogitBehavDialog final : public wxDialog { void Start(); public: - LogitBehavDialog(wxWindow *p_parent, GameDocument *p_doc); + LogitBehavDialog(wxWindow *p_parent, const std::shared_ptr &p_doc); ~LogitBehavDialog() override; }; diff --git a/src/gui/dlgameprop.cc b/src/gui/dlgameprop.cc index 8deaa1ef9c..40b168646e 100644 --- a/src/gui/dlgameprop.cc +++ b/src/gui/dlgameprop.cc @@ -75,7 +75,7 @@ class PlayerLabelPanel final : public wxPanel { }; private: - GameDocument *m_doc; + std::shared_ptr m_doc; std::vector m_rows; wxColour m_defaultBg; std::function m_onChanged; @@ -87,7 +87,7 @@ class PlayerLabelPanel final : public wxPanel { wxBitmapButton *MakeColorButton(int p_index); public: - PlayerLabelPanel(wxWindow *p_parent, GameDocument *p_doc, + PlayerLabelPanel(wxWindow *p_parent, const std::shared_ptr &p_doc, const std::function &p_onChanged); int NumRows() const { return static_cast(m_rows.size()); } @@ -106,7 +106,7 @@ class PlayerLabelPanel final : public wxPanel { wxString ValidateLabels(); }; -PlayerLabelPanel::PlayerLabelPanel(wxWindow *p_parent, GameDocument *p_doc, +PlayerLabelPanel::PlayerLabelPanel(wxWindow *p_parent, const std::shared_ptr &p_doc, const std::function &p_onChanged) : wxPanel(p_parent, wxID_ANY), m_doc(p_doc), m_onChanged(p_onChanged) { @@ -247,7 +247,8 @@ wxString PlayerLabelPanel::ValidateLabels() // class GamePropertiesDialog //======================================================================== -GamePropertiesDialog::GamePropertiesDialog(wxWindow *p_parent, GameDocument *p_doc) +GamePropertiesDialog::GamePropertiesDialog(wxWindow *p_parent, + const std::shared_ptr &p_doc) : wxDialog(p_parent, wxID_ANY, _("Game properties"), wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER), m_doc(p_doc) diff --git a/src/gui/dlgameprop.h b/src/gui/dlgameprop.h index d89533a4b6..92db9f6791 100644 --- a/src/gui/dlgameprop.h +++ b/src/gui/dlgameprop.h @@ -28,7 +28,7 @@ namespace Gambit::GUI { class PlayerLabelPanel; class GamePropertiesDialog final : public wxDialog { - GameDocument *m_doc; + std::shared_ptr m_doc; wxTextCtrl *m_title, *m_comment; PlayerLabelPanel *m_playerPanel; wxStaticText *m_errorText; @@ -37,7 +37,7 @@ class GamePropertiesDialog final : public wxDialog { public: // Lifecycle - GamePropertiesDialog(wxWindow *p_parent, GameDocument *p_doc); + GamePropertiesDialog(wxWindow *p_parent, const std::shared_ptr &p_doc); // Data access (only valid when ShowModal() returns with wxID_OK) wxString GetTitle() const override { return m_title->GetValue(); } diff --git a/src/gui/dlinsertmove.cc b/src/gui/dlinsertmove.cc index b0d18e1097..d8e0d3aed0 100644 --- a/src/gui/dlinsertmove.cc +++ b/src/gui/dlinsertmove.cc @@ -32,7 +32,7 @@ namespace Gambit::GUI { // InsertMoveDialog: Member functions //========================================================================= -InsertMoveDialog::InsertMoveDialog(wxWindow *p_parent, GameDocument *p_doc) +InsertMoveDialog::InsertMoveDialog(wxWindow *p_parent, const std::shared_ptr &p_doc) : wxDialog(p_parent, wxID_ANY, _("Insert Move"), wxDefaultPosition), m_doc(p_doc) { m_playerItem = new wxChoice(this, wxID_ANY); diff --git a/src/gui/dlinsertmove.h b/src/gui/dlinsertmove.h index 44616a313f..d9c8dbd7bd 100644 --- a/src/gui/dlinsertmove.h +++ b/src/gui/dlinsertmove.h @@ -29,7 +29,7 @@ namespace Gambit::GUI { class InsertMoveDialog final : public wxDialog { - GameDocument *m_doc; + std::shared_ptr m_doc; wxChoice *m_playerItem, *m_infosetItem; wxSpinCtrl *m_actions; @@ -39,7 +39,7 @@ class InsertMoveDialog final : public wxDialog { public: // Constructor - InsertMoveDialog(wxWindow *, GameDocument *); + InsertMoveDialog(wxWindow *, const std::shared_ptr &); // Data access (only valid if ShowModal() returns wxID_OK. // If GetInfoset() returns null, user selected "new infoset" diff --git a/src/gui/dlnash.cc b/src/gui/dlnash.cc index 50ebef0473..d16229b5eb 100644 --- a/src/gui/dlnash.cc +++ b/src/gui/dlnash.cc @@ -291,7 +291,7 @@ wxString ParameterDescription(const NashMethodSpec &p_method) } // namespace -NashChoiceDialog::NashChoiceDialog(wxWindow *p_parent, GameDocument *p_doc) +NashChoiceDialog::NashChoiceDialog(wxWindow *p_parent, const std::shared_ptr &p_doc) : wxDialog(p_parent, wxID_ANY, wxT("Compute Nash equilibria"), wxDefaultPosition), m_doc(p_doc) { auto *topSizer = new wxBoxSizer(wxVERTICAL); @@ -432,10 +432,10 @@ std::shared_ptr NashChoiceDialog::GetCommand() const const bool useBehavior = computation.representation == NashRepresentation::Behavior; std::shared_ptr output; if (UsesRationalOutput(computation.method)) { - output = std::make_shared>(m_doc, useBehavior); + output = std::make_shared>(m_doc.get(), useBehavior); } else { - output = std::make_shared>(m_doc, useBehavior); + output = std::make_shared>(m_doc.get(), useBehavior); } wxString count; diff --git a/src/gui/dlnash.h b/src/gui/dlnash.h index ee55b3881f..71e99c6559 100644 --- a/src/gui/dlnash.h +++ b/src/gui/dlnash.h @@ -27,7 +27,7 @@ namespace Gambit::GUI { class NashChoiceDialog final : public wxDialog { - GameDocument *m_doc; + std::shared_ptr m_doc; wxChoice *m_countChoice, *m_methodChoice, *m_repChoice; // Event handlers @@ -38,7 +38,7 @@ class NashChoiceDialog final : public wxDialog { NashComputationSpec GetComputation() const; public: - NashChoiceDialog(wxWindow *, GameDocument *); + NashChoiceDialog(wxWindow *, const std::shared_ptr &); std::shared_ptr GetCommand() const; diff --git a/src/gui/dlnashmon.cc b/src/gui/dlnashmon.cc index b1491f5534..91746bf96a 100644 --- a/src/gui/dlnashmon.cc +++ b/src/gui/dlnashmon.cc @@ -302,7 +302,7 @@ class SolverThreadRunner final : public wxThread { }; class NashMonitorDialog final : public wxDialog { - GameDocument *m_doc; + std::shared_ptr m_doc; std::unique_ptr m_runner; std::unique_ptr m_threadRunner; wxWindow *m_profileList; @@ -354,11 +354,12 @@ class NashMonitorDialog final : public wxDialog { } public: - NashMonitorDialog(wxWindow *p_parent, GameDocument *p_doc, + NashMonitorDialog(wxWindow *p_parent, const std::shared_ptr &p_doc, const std::shared_ptr &p_command); }; -NashMonitorDialog::NashMonitorDialog(wxWindow *p_parent, GameDocument *p_doc, +NashMonitorDialog::NashMonitorDialog(wxWindow *p_parent, + const std::shared_ptr &p_doc, const std::shared_ptr &p_command) : wxDialog(p_parent, wxID_ANY, wxT("Computing Nash equilibria"), wxDefaultPosition), m_doc(p_doc), m_output(p_command) @@ -547,7 +548,7 @@ void NashMonitorDialog::OnClose(wxCloseEvent &p_event) namespace Gambit::GUI { -void ShowNashMonitorDialog(wxWindow *p_parent, GameDocument *p_doc, +void ShowNashMonitorDialog(wxWindow *p_parent, const std::shared_ptr &p_doc, const std::shared_ptr &p_command) { NashMonitorDialog dialog(p_parent, p_doc, p_command); diff --git a/src/gui/dlnfglogit.cc b/src/gui/dlnfglogit.cc index 1a576e1a3d..51d1e314bf 100644 --- a/src/gui/dlnfglogit.cc +++ b/src/gui/dlnfglogit.cc @@ -46,14 +46,14 @@ namespace Gambit::GUI { //! isn't resized/repainted on every single profile received. //! class LogitMixedTable final : public wxGridTableBase { - GameDocument *m_doc; + std::shared_ptr m_doc; Array m_lambdas; Array>> m_profiles; int m_numCols{0}; int m_numRows{0}; public: - explicit LogitMixedTable(GameDocument *p_doc) : m_doc(p_doc) {} + explicit LogitMixedTable(const std::shared_ptr &p_doc) : m_doc(p_doc) {} int GetNumberRows() override { return m_numRows; } int GetNumberCols() override { return m_numCols; } @@ -175,7 +175,7 @@ class LogitMixedGrid final : public wxGrid { //@} public: - LogitMixedGrid(wxWindow *p_parent, GameDocument *p_doc) + LogitMixedGrid(wxWindow *p_parent, const std::shared_ptr &p_doc) : wxGrid(p_parent, wxID_ANY), m_gridTable(new LogitMixedTable(p_doc)) { SetTable(m_gridTable, true); @@ -261,7 +261,7 @@ class LogitMixedThreadRunner final : public wxThread { void RequestCancel() { m_cancel.RequestCancel(); } }; -LogitMixedDialog::LogitMixedDialog(wxWindow *p_parent, GameDocument *p_doc) +LogitMixedDialog::LogitMixedDialog(wxWindow *p_parent, const std::shared_ptr &p_doc) : wxDialog(p_parent, wxID_ANY, wxT("Compute quantal response equilibria"), wxDefaultPosition), m_doc(p_doc), m_mixedList(new LogitMixedGrid(this, m_doc)) { @@ -412,7 +412,7 @@ void LogitMixedDialog::OnSave(wxCommandEvent &) } } -void LogitStrategic(wxWindow *p_parent, GameDocument *p_doc) +void LogitStrategic(wxWindow *p_parent, const std::shared_ptr &p_doc) { LogitMixedDialog(p_parent, p_doc).ShowModal(); } diff --git a/src/gui/dlnfglogit.h b/src/gui/dlnfglogit.h index 5cae090259..28788e1f83 100644 --- a/src/gui/dlnfglogit.h +++ b/src/gui/dlnfglogit.h @@ -30,7 +30,7 @@ class LogitMixedGrid; class LogitMixedThreadRunner; class LogitMixedDialog final : public wxDialog { - GameDocument *m_doc; + std::shared_ptr m_doc; std::unique_ptr m_runner; LogitMixedGrid *m_mixedList; wxStaticText *m_statusText; @@ -47,11 +47,11 @@ class LogitMixedDialog final : public wxDialog { void Start(); public: - LogitMixedDialog(wxWindow *p_parent, GameDocument *p_doc); + LogitMixedDialog(wxWindow *p_parent, const std::shared_ptr &p_doc); ~LogitMixedDialog() override; }; -void LogitStrategic(wxWindow *p_parent, GameDocument *p_doc); +void LogitStrategic(wxWindow *p_parent, const std::shared_ptr &p_doc); } // namespace Gambit::GUI diff --git a/src/gui/efgdisplay.cc b/src/gui/efgdisplay.cc index c4648ac3a9..09fe1f3b39 100644 --- a/src/gui/efgdisplay.cc +++ b/src/gui/efgdisplay.cc @@ -62,7 +62,7 @@ END_EVENT_TABLE() // EfgDisplay: Constructor and destructor //---------------------------------------------------------------------- -EfgDisplay::EfgDisplay(wxWindow *p_parent, GameDocument *p_doc) +EfgDisplay::EfgDisplay(wxWindow *p_parent, const std::shared_ptr &p_doc) : wxScrolledWindow(p_parent), GameView(p_doc), m_layout(p_doc), m_zoom(100) { wxWindow::SetBackgroundColour(wxColour(250, 250, 250)); diff --git a/src/gui/efgdisplay.h b/src/gui/efgdisplay.h index 2731b0103b..76b84465c0 100644 --- a/src/gui/efgdisplay.h +++ b/src/gui/efgdisplay.h @@ -125,7 +125,7 @@ class EfgDisplay final : public wxScrolledWindow, public GameView { void ZoomByFactor(double p_factor, const wxPoint &p_clientPoint); public: - EfgDisplay(wxWindow *p_parent, GameDocument *p_doc); + EfgDisplay(wxWindow *p_parent, const std::shared_ptr &p_doc); void OnDraw(wxDC &dc) override; void OnDraw(wxDC &, double); diff --git a/src/gui/efgdragdrop.cc b/src/gui/efgdragdrop.cc index eaa5a785a5..0288a8abc3 100644 --- a/src/gui/efgdragdrop.cc +++ b/src/gui/efgdragdrop.cc @@ -41,7 +41,7 @@ namespace Gambit::GUI { class TreeDropTarget : public wxTextDropTarget { EfgDisplay *m_owner; - GameDocument *m_model; + std::shared_ptr m_model; bool OnDropOutcome(const GameNode &p_node, const wxString &p_text, const wxPoint &p_pos); bool OnDropTreeNode(const GameNode &p_node, const wxString &p_text, const wxPoint &p_pos); diff --git a/src/gui/efglayout.h b/src/gui/efglayout.h index 784258e0ab..1767af92f9 100644 --- a/src/gui/efglayout.h +++ b/src/gui/efglayout.h @@ -177,7 +177,7 @@ class TreeLayout final : public GameView { void BuildNodeList(const Game &); public: - explicit TreeLayout(GameDocument *p_doc) : GameView(p_doc) {} + explicit TreeLayout(const std::shared_ptr &p_doc) : GameView(p_doc) {} ~TreeLayout() override = default; void Layout(const Game &); diff --git a/src/gui/efgpanel.cc b/src/gui/efgpanel.cc index 18abd1e599..6f4a739669 100644 --- a/src/gui/efgpanel.cc +++ b/src/gui/efgpanel.cc @@ -47,7 +47,7 @@ EVT_MENU(GBT_MENU_VIEW_ZOOM100, EfgPanel::OnViewZoom100) EVT_MENU(GBT_MENU_VIEW_ZOOMFIT, EfgPanel::OnViewZoomFit) END_EVENT_TABLE() -EfgPanel::EfgPanel(wxWindow *p_parent, GameDocument *p_doc) +EfgPanel::EfgPanel(wxWindow *p_parent, const std::shared_ptr &p_doc) : wxPanel(p_parent, wxID_ANY), GameView(p_doc), m_treeWindow(new EfgDisplay(this, m_doc)) { auto *topSizer = new wxBoxSizer(wxVERTICAL); diff --git a/src/gui/efgpanel.h b/src/gui/efgpanel.h index b2bf2c6638..15710530e2 100644 --- a/src/gui/efgpanel.h +++ b/src/gui/efgpanel.h @@ -45,7 +45,7 @@ class EfgPanel final : public wxPanel, public GameView { //@} public: - EfgPanel(wxWindow *p_parent, GameDocument *p_doc); + EfgPanel(wxWindow *p_parent, const std::shared_ptr &p_doc); ~EfgPanel() override = default; /// @name Exporting/printing graphics diff --git a/src/gui/efgprofile.cc b/src/gui/efgprofile.cc index f596a3bbfb..43336acfef 100644 --- a/src/gui/efgprofile.cc +++ b/src/gui/efgprofile.cc @@ -32,7 +32,8 @@ namespace Gambit::GUI { // class BehaviorProfileList: Member functions //------------------------------------------------------------------------- -MixedBehaviorProfileList::MixedBehaviorProfileList(wxWindow *p_parent, GameDocument *p_doc) +MixedBehaviorProfileList::MixedBehaviorProfileList(wxWindow *p_parent, + const std::shared_ptr &p_doc) : wxGrid(p_parent, wxID_ANY), GameView(p_doc) { CreateGrid(0, 0); @@ -77,7 +78,7 @@ void MixedBehaviorProfileList::OnSelectCell(wxGridEvent &p_event) ClearSelection(); } -static wxColour GetPlayerColor(const GameDocument *p_doc, int p_index) +static wxColour GetPlayerColor(const std::shared_ptr &p_doc, int p_index) { const GameAction action = p_doc->GetAction(p_index + 1); return p_doc->GetStyle().GetPlayerColor(action->GetInfoset()->GetPlayer()); diff --git a/src/gui/efgprofile.h b/src/gui/efgprofile.h index ca492469cd..f21925bc9b 100644 --- a/src/gui/efgprofile.h +++ b/src/gui/efgprofile.h @@ -42,7 +42,7 @@ class MixedBehaviorProfileList final : public wxGrid, public GameView { void OnUpdate() override; public: - MixedBehaviorProfileList(wxWindow *p_parent, GameDocument *p_doc); + MixedBehaviorProfileList(wxWindow *p_parent, const std::shared_ptr &p_doc); ~MixedBehaviorProfileList() override; }; } // namespace Gambit::GUI diff --git a/src/gui/efgtooltip.cc b/src/gui/efgtooltip.cc index dd3d4d09ba..96c41b29c2 100644 --- a/src/gui/efgtooltip.cc +++ b/src/gui/efgtooltip.cc @@ -36,7 +36,7 @@ namespace Gambit::GUI { class OutcomeEditorPopup : public wxPopupTransientWindow { public: - OutcomeEditorPopup(EfgDisplay *p_owner, GameDocument *p_doc); + OutcomeEditorPopup(EfgDisplay *p_owner, const std::shared_ptr &p_doc); void BeginEdit(const GameNode &p_node, int p_initialPlayer = 0); bool Commit(); @@ -63,7 +63,7 @@ class OutcomeEditorPopup : public wxPopupTransientWindow { void RestoreAfterFailedCommit(wxTextCtrl *p_invalidCtrl); EfgDisplay *m_owner; - GameDocument *m_doc; + std::shared_ptr m_doc; GameNode m_node; @@ -80,7 +80,8 @@ class OutcomeEditorPopup : public wxPopupTransientWindow { bool m_restoringAfterFailedCommit{false}; }; -OutcomeEditorPopup::OutcomeEditorPopup(EfgDisplay *p_owner, GameDocument *p_doc) +OutcomeEditorPopup::OutcomeEditorPopup(EfgDisplay *p_owner, + const std::shared_ptr &p_doc) : wxPopupTransientWindow(p_owner, wxBORDER_NONE), m_owner(p_owner), m_doc(p_doc), m_contentPanel(nullptr), m_labelCtrl(nullptr), m_errorText(nullptr) { @@ -423,7 +424,7 @@ void OutcomeEditorPopup::RestoreAfterFailedCommit(wxTextCtrl *p_invalidCtrl) // left-hand player panel. class NodeInfoPopup : public wxPopupTransientWindow { public: - NodeInfoPopup(EfgDisplay *p_owner, GameDocument *p_doc); + NodeInfoPopup(EfgDisplay *p_owner, const std::shared_ptr &p_doc); void ShowForNode(const GameNode &p_node); @@ -432,7 +433,7 @@ class NodeInfoPopup : public wxPopupTransientWindow { void PositionPopup(); EfgDisplay *m_owner; - GameDocument *m_doc; + std::shared_ptr m_doc; GameNode m_node; @@ -452,7 +453,7 @@ class NodeInfoPopup : public wxPopupTransientWindow { wxStaticText *m_beliefText; }; -NodeInfoPopup::NodeInfoPopup(EfgDisplay *p_owner, GameDocument *p_doc) +NodeInfoPopup::NodeInfoPopup(EfgDisplay *p_owner, const std::shared_ptr &p_doc) : wxPopupTransientWindow(p_owner, wxBORDER_NONE), m_owner(p_owner), m_doc(p_doc) { SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_BTNSHADOW)); diff --git a/src/gui/gamedoc.cc b/src/gui/gamedoc.cc index 53065bb2fb..f37fdb77a3 100644 --- a/src/gui/gamedoc.cc +++ b/src/gui/gamedoc.cc @@ -214,32 +214,44 @@ GameDocument::GameDocument(Game p_game) GameDocument::~GameDocument() { wxGetApp().RemoveDocument(this); } -bool GameDocument::LoadWorkspace(const wxString &p_filename) +GameDocument::LoadOutcome GameDocument::Load(const wxString &p_filename) { std::ifstream input(p_filename.mb_str()); if (!input) { - return false; + return {LoadResult::OpenFailed, nullptr}; } - LegacyWorkspaceFile workspace; + // First, see whether this is a Gambit workspace (.gbt) file. try { - workspace = Gambit::ReadLegacyWorkspace(input); + const LegacyWorkspaceFile workspace = ReadLegacyWorkspace(input); + std::istringstream game_text(workspace.game); + auto doc = std::make_shared(ReadGame(game_text)); + if (doc->m_workspace.Load(workspace.analyses)) { + doc->m_style.Load(workspace); + doc->SetFilename(p_filename); + return {LoadResult::Success, doc}; + } } - catch (const std::runtime_error &) { - return false; + catch (const std::exception &) { + // Not a recognized (or not fully valid) workspace file; fall through to + // try it as a bare game file instead. } + + // Not a (valid) workspace -- try reading it as a bare .efg/.nfg file. + input.clear(); + input.seekg(0); try { - std::istringstream game_text(workspace.game); - m_game = ReadGame(game_text); - } - catch (...) { - return false; + const Game game = ReadGame(input); + if (game->IsAgg()) { + return {LoadResult::UnsupportedRepresentation, nullptr}; + } + auto doc = std::make_shared(game); + doc->SetFilename(p_filename); + return {LoadResult::Success, doc}; } - if (!m_workspace.Load(workspace.analyses)) { - return false; + catch (const InvalidFileException &) { + return {LoadResult::ParseFailed, nullptr}; } - m_style.Load(workspace); - return true; } void GameDocument::SaveWorkspace(std::ostream &p_file) const diff --git a/src/gui/gamedoc.h b/src/gui/gamedoc.h index d501ad072e..580ccb6ad5 100644 --- a/src/gui/gamedoc.h +++ b/src/gui/gamedoc.h @@ -214,9 +214,6 @@ class GameDocument { void RemoveView(GameView *p_view) { m_views.erase(std::find(m_views.begin(), m_views.end(), p_view)); - if (m_views.empty()) { - delete this; - } } Game m_game; @@ -232,15 +229,23 @@ class GameDocument { public: explicit GameDocument(Game p_game); + GameDocument(const GameDocument &) = delete; + GameDocument &operator=(const GameDocument &) = delete; ~GameDocument(); //! //! @name Reading and writing savefiles //! //@{ - /// Load workspace from the specified file (which should be a .gbt file) - /// Returns true if successful, false if error - bool LoadWorkspace(const wxString &p_filename); + enum class LoadResult { Success, OpenFailed, ParseFailed, UnsupportedRepresentation }; + struct LoadOutcome { + LoadResult result; + std::shared_ptr document; ///< non-null iff result == LoadResult::Success + }; + /// Attempt to construct a document by reading `p_filename`, trying it first as a + /// Gambit workspace (.gbt) and then as a bare game (.efg/.nfg) file. + static LoadOutcome Load(const wxString &p_filename); + void SaveWorkspace(std::ostream &) const; //@} @@ -345,16 +350,16 @@ class GameDocument { void DoAnalysisOutputChanged(); }; -inline GameDocument *NewTreeDocument() +inline std::shared_ptr NewTreeDocument() { const Game efg = NewTree(); efg->SetTitle("Untitled Extensive Game"); efg->NewPlayer("Player 1"); efg->NewPlayer("Player 2"); - return new GameDocument(efg); + return std::make_shared(efg); } -inline GameDocument *NewTableDocument(const std::vector &p_dim) +inline std::shared_ptr NewTableDocument(const std::vector &p_dim) { const Game nfg = NewTable(p_dim); nfg->SetTitle("Untitled Strategic Game"); @@ -364,15 +369,18 @@ inline GameDocument *NewTableDocument(const std::vector &p_dim) labels[player->GetLabel()] = "Player " + std::to_string(pl++); } nfg->RelabelPlayers(labels); - return new GameDocument(nfg); + return std::make_shared(nfg); } class GameView { protected: - GameDocument *m_doc; + std::shared_ptr m_doc; public: - explicit GameView(GameDocument *p_doc) : m_doc(p_doc) { m_doc->AddView(this); } + explicit GameView(const std::shared_ptr &p_doc) : m_doc(p_doc) + { + m_doc->AddView(this); + } virtual ~GameView() { m_doc->RemoveView(this); } virtual void OnUpdate() = 0; @@ -380,7 +388,7 @@ class GameView { /// Post any pending changes in the viewer to the document virtual void PostPendingChanges() {} - GameDocument *GetDocument() const { return m_doc; } + std::shared_ptr GetDocument() const { return m_doc; } }; } // namespace Gambit::GUI diff --git a/src/gui/gameframe.cc b/src/gui/gameframe.cc index 0ca2531f4c..fd9de2b3d5 100644 --- a/src/gui/gameframe.cc +++ b/src/gui/gameframe.cc @@ -71,12 +71,12 @@ class ProfileListPanel final : public wxPanel, public GameView { void OnUpdate() override {} public: - ProfileListPanel(wxWindow *p_parent, GameDocument *p_doc); + ProfileListPanel(wxWindow *p_parent, const std::shared_ptr &p_doc); void ShowMixed(bool p_show); }; -ProfileListPanel::ProfileListPanel(wxWindow *p_parent, GameDocument *p_doc) +ProfileListPanel::ProfileListPanel(wxWindow *p_parent, const std::shared_ptr &p_doc) : wxPanel(p_parent, wxID_ANY), GameView(p_doc) { auto *topSizer = new wxBoxSizer(wxHORIZONTAL); @@ -124,12 +124,12 @@ class AnalysisNotebook final : public wxPanel, public GameView { void OnUpdate() override; public: - AnalysisNotebook(wxWindow *p_parent, GameDocument *p_doc); + AnalysisNotebook(wxWindow *p_parent, const std::shared_ptr &p_doc); void ShowMixed(bool p_show); }; -AnalysisNotebook::AnalysisNotebook(wxWindow *p_parent, GameDocument *p_doc) +AnalysisNotebook::AnalysisNotebook(wxWindow *p_parent, const std::shared_ptr &p_doc) : wxPanel(p_parent, wxID_ANY), GameView(p_doc), m_profiles(new ProfileListPanel(this, p_doc)) { m_choices = new wxChoice(this, wxID_ANY); @@ -220,7 +220,7 @@ END_EVENT_TABLE() // GameFrame: Constructor and destructor //--------------------------------------------------------------------- -GameFrame::GameFrame(wxWindow *p_parent, GameDocument *p_doc) +GameFrame::GameFrame(wxWindow *p_parent, const std::shared_ptr &p_doc) : wxFrame(p_parent, wxID_ANY, _T(""), wxDefaultPosition, wxDefaultSize), GameView(p_doc) { const wxRect area = wxGetClientDisplayRect(); @@ -1080,7 +1080,7 @@ void GameFrame::OnToolsDominance(wxCommandEvent &p_event) } } -extern void ShowNashMonitorDialog(wxWindow *p_parent, GameDocument *p_doc, +extern void ShowNashMonitorDialog(wxWindow *p_parent, const std::shared_ptr &p_doc, const std::shared_ptr &p_command); void GameFrame::OnToolsEquilibrium(wxCommandEvent &) @@ -1126,7 +1126,7 @@ void GameFrame::OnToolsEquilibrium(wxCommandEvent &) } } -extern void LogitStrategic(wxWindow *, GameDocument *); +extern void LogitStrategic(wxWindow *, const std::shared_ptr &); void GameFrame::OnToolsQre(wxCommandEvent &) { @@ -1169,7 +1169,7 @@ struct CloseWarningText { wxString secondary; }; -CloseWarningText CloseWarningMessage(GameDocument *p_doc) +CloseWarningText CloseWarningMessage(const std::shared_ptr &p_doc) { const bool gameModified = p_doc->IsGameModified(); const bool workspaceModified = p_doc->IsWorkspaceModified(); diff --git a/src/gui/gameframe.h b/src/gui/gameframe.h index 1c4131f851..8b0cc628a5 100644 --- a/src/gui/gameframe.h +++ b/src/gui/gameframe.h @@ -99,7 +99,7 @@ class GameFrame final : public wxFrame, public GameView { public: // CONSTRUCTOR AND DESTRUCTOR - GameFrame(wxWindow *p_parent, GameDocument *p_doc); + GameFrame(wxWindow *p_parent, const std::shared_ptr &p_doc); ~GameFrame() override; DECLARE_EVENT_TABLE() diff --git a/src/gui/nfgheadergrid.cc b/src/gui/nfgheadergrid.cc index 15f979c6f9..7f324bbca6 100644 --- a/src/gui/nfgheadergrid.cc +++ b/src/gui/nfgheadergrid.cc @@ -271,7 +271,7 @@ int GetGlobalStrategyIndex(const GameStrategy &p_strategy) return index + p_strategy->GetNumber(); } -bool HasStrategyInfo(GameDocument *p_doc, const GameStrategy &p_strategy) +bool HasStrategyInfo(const std::shared_ptr &p_doc, const GameStrategy &p_strategy) { return !GetStrategyDescription(p_strategy).empty() || p_doc->GetWorkspace().GetCurrentProfile() > 0; @@ -291,7 +291,7 @@ class StrategyInfoPopup final : public wxPopupTransientWindow { BuildControls(); } - void ShowForStrategy(GameDocument *p_doc, const GameStrategy &p_strategy, + void ShowForStrategy(const std::shared_ptr &p_doc, const GameStrategy &p_strategy, const wxPoint &p_anchor) { const wxString label = wxString::FromUTF8(p_strategy->GetLabel()); @@ -488,7 +488,7 @@ void RowPlayerGrid::OnHoverTimer(wxTimerEvent &) const int player = m_table->GetRowHeaderPlayer(m_hoverCol); const int strategy = m_table->GetRowHeaderStrategy(m_hoverCol, m_hoverRow); const auto gameStrategy = m_table->GetStrategyByPlayerAndIndex(player, strategy); - GameDocument *doc = m_table->GetDocument(); + const std::shared_ptr doc = m_table->GetDocument(); if (!HasStrategyInfo(doc, gameStrategy)) { return; } @@ -862,7 +862,7 @@ void ColPlayerGrid::OnHoverTimer(wxTimerEvent &) const int player = m_table->GetColHeaderPlayer(m_hoverRow); const int strategy = m_table->GetColHeaderStrategy(m_hoverRow, m_hoverCol); const auto gameStrategy = m_table->GetStrategyByPlayerAndIndex(player, strategy); - GameDocument *doc = m_table->GetDocument(); + const std::shared_ptr doc = m_table->GetDocument(); if (!HasStrategyInfo(doc, gameStrategy)) { return; } diff --git a/src/gui/nfgpanel.cc b/src/gui/nfgpanel.cc index 0446c56a1a..3866b971f5 100644 --- a/src/gui/nfgpanel.cc +++ b/src/gui/nfgpanel.cc @@ -53,7 +53,7 @@ class StrategyDominanceToolbar final : public wxPanel, public GameView { void OnLastLevel(wxCommandEvent &); public: - StrategyDominanceToolbar(wxWindow *p_parent, GameDocument *p_doc); + StrategyDominanceToolbar(wxWindow *p_parent, const std::shared_ptr &p_doc); ~StrategyDominanceToolbar() override = default; }; @@ -62,7 +62,8 @@ class StrategyDominanceToolbar final : public wxPanel, public GameView { #include "bitmaps/tobegin.xpm" #include "bitmaps/toend.xpm" -StrategyDominanceToolbar::StrategyDominanceToolbar(wxWindow *p_parent, GameDocument *p_doc) +StrategyDominanceToolbar::StrategyDominanceToolbar(wxWindow *p_parent, + const std::shared_ptr &p_doc) : wxPanel(p_parent, wxID_ANY), GameView(p_doc) { auto *topSizer = new wxBoxSizer(wxHORIZONTAL); @@ -160,7 +161,8 @@ BEGIN_EVENT_TABLE(NfgPanel, wxPanel) EVT_MENU(GBT_MENU_TOOLS_DOMINANCE, NfgPanel::OnToolsDominance) END_EVENT_TABLE() -NfgPanel::NfgPanel(wxWindow *p_parent, GameDocument *p_doc, bool p_showDominance) +NfgPanel::NfgPanel(wxWindow *p_parent, const std::shared_ptr &p_doc, + bool p_showDominance) : wxPanel(p_parent, wxID_ANY), GameView(p_doc), m_dominanceToolbar(new StrategyDominanceToolbar(this, m_doc)), m_tableWidget(new TableWidget(this, wxID_ANY, m_doc)) diff --git a/src/gui/nfgpanel.h b/src/gui/nfgpanel.h index d97209989c..d6e4c359ea 100644 --- a/src/gui/nfgpanel.h +++ b/src/gui/nfgpanel.h @@ -35,7 +35,7 @@ class NfgPanel final : public wxPanel, public GameView { void OnToolsDominance(wxCommandEvent &); public: - NfgPanel(wxWindow *p_parent, GameDocument *p_doc, bool p_showDominance); + NfgPanel(wxWindow *p_parent, const std::shared_ptr &p_doc, bool p_showDominance); ~NfgPanel() override = default; bool IsDominanceShown() const { return m_dominanceToolbar->IsShown(); } diff --git a/src/gui/nfgprofile.cc b/src/gui/nfgprofile.cc index 1a83908c5e..855b8990fd 100644 --- a/src/gui/nfgprofile.cc +++ b/src/gui/nfgprofile.cc @@ -32,7 +32,8 @@ namespace Gambit::GUI { // class MixedProfileList: Member functions //------------------------------------------------------------------------- -MixedStrategyProfileList::MixedStrategyProfileList(wxWindow *p_parent, GameDocument *p_doc) +MixedStrategyProfileList::MixedStrategyProfileList(wxWindow *p_parent, + const std::shared_ptr &p_doc) : wxGrid(p_parent, wxID_ANY), GameView(p_doc) { CreateGrid(0, 0); @@ -78,7 +79,7 @@ void MixedStrategyProfileList::OnSelectCell(wxGridEvent &p_event) } #ifdef UNUSED -static Gambit::GameStrategy GetStrategy(GameDocument *p_doc, int p_index) +static Gambit::GameStrategy GetStrategy(const std::shared_ptr &p_doc, int p_index) { int index = 0; for (int pl = 1; pl <= p_doc->GetGame()->NumPlayers(); pl++) { @@ -93,7 +94,7 @@ static Gambit::GameStrategy GetStrategy(GameDocument *p_doc, int p_index) } #endif -static wxColour GetPlayerColor(const GameDocument *p_doc, int p_index) +static wxColour GetPlayerColor(const std::shared_ptr &p_doc, int p_index) { int index = 0; for (const auto &player : p_doc->GetGame()->GetPlayers()) { diff --git a/src/gui/nfgprofile.h b/src/gui/nfgprofile.h index c9029460c0..76d708ea7f 100644 --- a/src/gui/nfgprofile.h +++ b/src/gui/nfgprofile.h @@ -42,7 +42,7 @@ class MixedStrategyProfileList final : public wxGrid, public GameView { void OnUpdate() override; public: - MixedStrategyProfileList(wxWindow *p_parent, GameDocument *p_doc); + MixedStrategyProfileList(wxWindow *p_parent, const std::shared_ptr &p_doc); ~MixedStrategyProfileList() override; }; } // namespace Gambit::GUI diff --git a/src/gui/nfgtable.cc b/src/gui/nfgtable.cc index 6406a6ac99..f8747eb098 100644 --- a/src/gui/nfgtable.cc +++ b/src/gui/nfgtable.cc @@ -93,7 +93,8 @@ bool TableWidget::IsColHeaderStrategyDominated(int headerRow, int headerCol, boo return support.IsDominated(GetStrategyByPlayerAndIndex(player, strat), strict); } -TableWidget::TableWidget(NfgPanel *p_parent, wxWindowID p_id, GameDocument *p_doc) +TableWidget::TableWidget(NfgPanel *p_parent, wxWindowID p_id, + const std::shared_ptr &p_doc) : wxPanel(p_parent, p_id), m_doc(p_doc), m_nfgPanel(p_parent), m_payoffGrid(nullptr), m_rowGrid(nullptr), m_colGrid(nullptr), m_layout(std::make_shared(p_doc)) { diff --git a/src/gui/nfgtable.h b/src/gui/nfgtable.h index 11a9a954bf..28a6ff7f08 100644 --- a/src/gui/nfgtable.h +++ b/src/gui/nfgtable.h @@ -76,7 +76,7 @@ class TableGridBase : public wxGrid { }; class StrategicTableLayout { - GameDocument *m_doc; + std::shared_ptr m_doc; std::vector m_rowPlayers; std::vector m_colPlayers; @@ -93,7 +93,7 @@ class StrategicTableLayout { } public: - explicit StrategicTableLayout(GameDocument *doc) : m_doc(doc) + explicit StrategicTableLayout(const std::shared_ptr &doc) : m_doc(doc) { if (m_doc->GetGame()->NumPlayers() >= 1) { m_rowPlayers.push_back(1); @@ -106,7 +106,7 @@ class StrategicTableLayout { } } - GameDocument *GetDocument() const { return m_doc; } + std::shared_ptr GetDocument() const { return m_doc; } /// Returns the number of players assigned to the rows int NumRowPlayers() const { return m_rowPlayers.size(); } @@ -283,7 +283,7 @@ class StrategicTableLayout { //! the display of row and column labels //! class TableWidget final : public wxPanel { - GameDocument *m_doc; + std::shared_ptr m_doc; NfgPanel *m_nfgPanel; wxGrid *m_payoffGrid, *m_rowGrid, *m_colGrid; @@ -341,7 +341,7 @@ class TableWidget final : public wxPanel { void SyncScrollFromPayoff(); public: - TableWidget(NfgPanel *p_parent, wxWindowID p_id, GameDocument *p_doc); + TableWidget(NfgPanel *p_parent, wxWindowID p_id, const std::shared_ptr &p_doc); /// @name Coordination of grids //@{ @@ -454,7 +454,7 @@ class TableWidget final : public wxPanel { bool IsReadOnly() const; wxColour GetPlayerColor(int player) const; const StrategySupportProfile &GetSupport() const { return m_doc->GetNfgSupport(); } - GameDocument *GetDocument() const { return m_doc; } + std::shared_ptr GetDocument() const { return m_doc; } bool IsRowHeaderStrategyDominated(int headerCol, int headerRow, bool strict) const; bool IsColHeaderStrategyDominated(int headerRow, int headerCol, bool strict) const;