From 359ef2ac40451ad82f50ac8f23072513f2c55b44 Mon Sep 17 00:00:00 2001 From: Theodore Turocy Date: Wed, 19 Aug 2026 15:59:04 +0100 Subject: [PATCH] Clarify ownership and creation model for game workspace documents in GUI. --- src/gui/app.cc | 51 +++++++++++++++-------------------------- src/gui/dlefglogit.cc | 8 +++---- src/gui/dlefglogit.h | 4 ++-- src/gui/dlgameprop.cc | 3 ++- src/gui/dlgameprop.h | 4 ++-- src/gui/dlinsertmove.cc | 2 +- src/gui/dlinsertmove.h | 4 ++-- src/gui/dlnash.cc | 6 ++--- src/gui/dlnash.h | 4 ++-- src/gui/dlnashmon.cc | 9 ++++---- src/gui/dlnfglogit.cc | 10 ++++---- src/gui/dlnfglogit.h | 6 ++--- src/gui/efgdisplay.cc | 11 +++++---- src/gui/efgdisplay.h | 2 +- src/gui/efglayout.h | 2 +- src/gui/efgpanel.cc | 19 ++++++++------- src/gui/efgpanel.h | 2 +- src/gui/efgprofile.cc | 5 ++-- src/gui/efgprofile.h | 2 +- src/gui/gamedoc.cc | 42 +++++++++++++++++++++------------ src/gui/gamedoc.h | 34 ++++++++++++++++----------- src/gui/gameframe.cc | 16 ++++++------- src/gui/gameframe.h | 2 +- src/gui/nfgpanel.cc | 21 +++++++++-------- src/gui/nfgpanel.h | 2 +- src/gui/nfgprofile.cc | 7 +++--- src/gui/nfgprofile.h | 2 +- src/gui/nfgtable.cc | 3 ++- src/gui/nfgtable.h | 10 ++++---- 29 files changed, 155 insertions(+), 138 deletions(-) diff --git a/src/gui/app.cc b/src/gui/app.cc index 90c53cf8a7..0ace3d36b1 100644 --- a/src/gui/app.cc +++ b/src/gui/app.cc @@ -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 0a4412f181..34bf5cfaad 100644 --- a/src/gui/dlgameprop.cc +++ b/src/gui/dlgameprop.cc @@ -33,7 +33,8 @@ namespace Gambit::GUI { // 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), m_doc(p_doc) { auto *topSizer = new wxBoxSizer(wxVERTICAL); diff --git a/src/gui/dlgameprop.h b/src/gui/dlgameprop.h index d22dc7ff91..001c16579b 100644 --- a/src/gui/dlgameprop.h +++ b/src/gui/dlgameprop.h @@ -25,12 +25,12 @@ namespace Gambit::GUI { class GamePropertiesDialog final : public wxDialog { - GameDocument *m_doc; + std::shared_ptr m_doc; wxTextCtrl *m_title, *m_comment; 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 dbfc58ebae..6cc7303b65 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 0666910f86..59cd2a5aa3 100644 --- a/src/gui/efgdisplay.cc +++ b/src/gui/efgdisplay.cc @@ -40,7 +40,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(); @@ -67,7 +67,7 @@ class OutcomeEditorPopup : public wxPopupTransientWindow { void RestoreAfterFailedCommit(wxTextCtrl *p_invalidCtrl); EfgDisplay *m_owner; - GameDocument *m_doc; + std::shared_ptr m_doc; GameNode m_node; @@ -84,7 +84,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) { @@ -445,7 +446,7 @@ static wxBitmap MakeOutcomeBitmap() class PlayerDropTarget : public wxTextDropTarget { EfgDisplay *m_owner; - GameDocument *m_model; + std::shared_ptr m_model; bool OnDropPlayer(const GameNode &p_node, const wxString &p_text, const wxPoint &p_pos); bool OnDropOutcome(const GameNode &p_node, const wxString &p_text, const wxPoint &p_pos); @@ -573,7 +574,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), m_outcomeEditor(new OutcomeEditorPopup(this, p_doc)) { diff --git a/src/gui/efgdisplay.h b/src/gui/efgdisplay.h index 897ad26172..f92a9deaa4 100644 --- a/src/gui/efgdisplay.h +++ b/src/gui/efgdisplay.h @@ -65,7 +65,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/efglayout.h b/src/gui/efglayout.h index b72a4c1697..de205f84a6 100644 --- a/src/gui/efglayout.h +++ b/src/gui/efglayout.h @@ -178,7 +178,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; GameNode PriorSameLevel(const GameNode &) const; diff --git a/src/gui/efgpanel.cc b/src/gui/efgpanel.cc index 8f4494d0d8..09e65dada1 100644 --- a/src/gui/efgpanel.cc +++ b/src/gui/efgpanel.cc @@ -76,7 +76,7 @@ void TreePlayerIcon::OnLeftClick(wxMouseEvent &) class TreePlayerPanel : public wxPanel { private: - GameDocument *m_doc; + std::shared_ptr m_doc; int m_player; EditableLabelText *m_playerLabel; wxStaticText *m_payoff, *m_nodeValue, *m_nodeProb; @@ -95,7 +95,7 @@ class TreePlayerPanel : public wxPanel { //@} public: - TreePlayerPanel(wxWindow *, GameDocument *, int p_player); + TreePlayerPanel(wxWindow *, const std::shared_ptr &, int p_player); void OnUpdate(); void PostPendingChanges(); @@ -107,7 +107,8 @@ BEGIN_EVENT_TABLE(TreePlayerPanel, wxPanel) EVT_CHAR(TreePlayerPanel::OnChar) END_EVENT_TABLE() -TreePlayerPanel::TreePlayerPanel(wxWindow *p_parent, GameDocument *p_doc, int p_player) +TreePlayerPanel::TreePlayerPanel(wxWindow *p_parent, const std::shared_ptr &p_doc, + int p_player) : wxPanel(p_parent, wxID_ANY), m_doc(p_doc), m_player(p_player) { auto *topSizer = new wxBoxSizer(wxVERTICAL); @@ -367,10 +368,11 @@ class gbtTreeChancePanel : public wxPanel, public GameView { //@} public: - gbtTreeChancePanel(wxWindow *, GameDocument *); + gbtTreeChancePanel(wxWindow *, const std::shared_ptr &); }; -gbtTreeChancePanel::gbtTreeChancePanel(wxWindow *p_parent, GameDocument *p_doc) +gbtTreeChancePanel::gbtTreeChancePanel(wxWindow *p_parent, + const std::shared_ptr &p_doc) : wxPanel(p_parent, wxID_ANY), GameView(p_doc) { auto *topSizer = new wxBoxSizer(wxVERTICAL); @@ -442,10 +444,11 @@ class gbtTreePlayerToolbar : public wxPanel, public GameView { //@} public: - gbtTreePlayerToolbar(wxWindow *p_parent, GameDocument *p_doc); + gbtTreePlayerToolbar(wxWindow *p_parent, const std::shared_ptr &p_doc); }; -gbtTreePlayerToolbar::gbtTreePlayerToolbar(wxWindow *p_parent, GameDocument *p_doc) +gbtTreePlayerToolbar::gbtTreePlayerToolbar(wxWindow *p_parent, + const std::shared_ptr &p_doc) : wxPanel(p_parent, wxID_ANY, wxDefaultPosition, wxSize(210, -1)), GameView(p_doc), m_chancePanel(new gbtTreeChancePanel(this, m_doc)) { @@ -499,7 +502,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)), m_playerToolbar(new gbtTreePlayerToolbar(this, m_doc)) { diff --git a/src/gui/efgpanel.h b/src/gui/efgpanel.h index b249593355..18dba16ad6 100644 --- a/src/gui/efgpanel.h +++ b/src/gui/efgpanel.h @@ -46,7 +46,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 de034d7396..764494bdf4 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); @@ -83,7 +84,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/gamedoc.cc b/src/gui/gamedoc.cc index 890e0b9bf6..bcffd59213 100644 --- a/src/gui/gamedoc.cc +++ b/src/gui/gamedoc.cc @@ -215,32 +215,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 a6d201032c..798b322016 100644 --- a/src/gui/gamedoc.h +++ b/src/gui/gamedoc.h @@ -213,9 +213,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; //@} @@ -339,16 +344,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"); @@ -356,15 +361,18 @@ inline GameDocument *NewTableDocument(const std::vector &p_dim) for (auto player : nfg->GetPlayers()) { player->SetLabel("Player " + std::to_string(pl++)); } - 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; @@ -372,7 +380,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 95ca37f271..6a8378e293 100644 --- a/src/gui/gameframe.cc +++ b/src/gui/gameframe.cc @@ -74,12 +74,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); @@ -127,12 +127,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); @@ -230,7 +230,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(); @@ -1225,7 +1225,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 &) @@ -1271,7 +1271,7 @@ void GameFrame::OnToolsEquilibrium(wxCommandEvent &) } } -extern void LogitStrategic(wxWindow *, GameDocument *); +extern void LogitStrategic(wxWindow *, const std::shared_ptr &); void GameFrame::OnToolsQre(wxCommandEvent &) { @@ -1314,7 +1314,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 189c924220..0c5763e25c 100644 --- a/src/gui/gameframe.h +++ b/src/gui/gameframe.h @@ -107,7 +107,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/nfgpanel.cc b/src/gui/nfgpanel.cc index f1c46da084..bb385066af 100644 --- a/src/gui/nfgpanel.cc +++ b/src/gui/nfgpanel.cc @@ -71,7 +71,7 @@ void TablePlayerIcon::OnLeftClick(wxMouseEvent &) } class TablePlayerPanel final : public wxPanel { - GameDocument *m_doc; + std::shared_ptr m_doc; int m_player; EditableLabelText *m_playerLabel; wxStaticText *m_payoff; @@ -91,7 +91,7 @@ class TablePlayerPanel final : public wxPanel { //@} public: - TablePlayerPanel(wxWindow *, NfgPanel *, GameDocument *, int p_player); + TablePlayerPanel(wxWindow *, NfgPanel *, const std::shared_ptr &, int p_player); void OnUpdate(); void PostPendingChanges(); @@ -103,8 +103,8 @@ BEGIN_EVENT_TABLE(TablePlayerPanel, wxPanel) EVT_CHAR(TablePlayerPanel::OnChar) END_EVENT_TABLE() -TablePlayerPanel::TablePlayerPanel(wxWindow *p_parent, NfgPanel *p_nfgPanel, GameDocument *p_doc, - int p_player) +TablePlayerPanel::TablePlayerPanel(wxWindow *p_parent, NfgPanel *p_nfgPanel, + const std::shared_ptr &p_doc, int p_player) : wxPanel(p_parent, wxID_ANY), /* m_nfgPanel(p_nfgPanel),*/ m_doc(p_doc), m_player(p_player) { @@ -262,7 +262,7 @@ class TablePlayerToolbar final : public wxPanel, public GameView { Array m_playerPanels; public: - TablePlayerToolbar(NfgPanel *p_parent, GameDocument *p_doc); + TablePlayerToolbar(NfgPanel *p_parent, const std::shared_ptr &p_doc); /// @name Implementation of GameView members //@{ @@ -271,7 +271,8 @@ class TablePlayerToolbar final : public wxPanel, public GameView { //@} }; -TablePlayerToolbar::TablePlayerToolbar(NfgPanel *p_parent, GameDocument *p_doc) +TablePlayerToolbar::TablePlayerToolbar(NfgPanel *p_parent, + const std::shared_ptr &p_doc) : wxPanel(p_parent, wxID_ANY, wxDefaultPosition, wxSize(210, -1)), GameView(p_doc), m_nfgPanel(p_parent) { @@ -332,7 +333,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; }; @@ -341,7 +342,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); @@ -439,7 +441,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_playerToolbar(new TablePlayerToolbar(this, m_doc)), diff --git a/src/gui/nfgpanel.h b/src/gui/nfgpanel.h index 78bbdec5b5..c3628607a8 100644 --- a/src/gui/nfgpanel.h +++ b/src/gui/nfgpanel.h @@ -37,7 +37,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 5c4502c829..5ba2569bd6 100644 --- a/src/gui/nfgtable.cc +++ b/src/gui/nfgtable.cc @@ -1202,7 +1202,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 9febb9ea98..105130b91b 100644 --- a/src/gui/nfgtable.h +++ b/src/gui/nfgtable.h @@ -30,7 +30,7 @@ class GameDocument; class NfgPanel; class StrategicTableLayout { - GameDocument *m_doc; + std::shared_ptr m_doc; std::vector m_rowPlayers; std::vector m_colPlayers; @@ -47,7 +47,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); @@ -60,7 +60,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(); } @@ -237,7 +237,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; @@ -279,7 +279,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 //@{