From 39406f2ba412610641ba12bd9cbb9b0605ef0283 Mon Sep 17 00:00:00 2001 From: Theodore Turocy Date: Fri, 29 May 2026 15:26:16 +0100 Subject: [PATCH 1/9] Set filename for .efg/.nfg files --- src/gui/app.cc | 2 +- src/gui/gameframe.cc | 10 ++++++---- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/gui/app.cc b/src/gui/app.cc index 2aa73677b..01f3525aa 100644 --- a/src/gui/app.cc +++ b/src/gui/app.cc @@ -148,7 +148,7 @@ AppLoadResult Application::LoadFile(const wxString &p_filename, wxWindow *p_pare m_fileHistory.AddFileToHistory(p_filename); m_fileHistory.Save(*wxConfigBase::Get()); doc = new GameDocument(nfg); - doc->SetFilename(""); + doc->SetFilename(p_filename); (void)new GameFrame(nullptr, doc); return GBT_APP_FILE_OK; } diff --git a/src/gui/gameframe.cc b/src/gui/gameframe.cc index 9d69701a8..d3249a874 100644 --- a/src/gui/gameframe.cc +++ b/src/gui/gameframe.cc @@ -653,10 +653,12 @@ void GameFrame::OnFileClose(wxCommandEvent &) { Close(); } void GameFrame::OnFileSave(wxCommandEvent &p_event) { if (p_event.GetId() == wxID_SAVEAS || m_doc->GetFilename().empty()) { - wxFileDialog dialog(this, _("Choose file"), wxPathOnly(m_doc->GetFilename()), - wxFileNameFromPath(m_doc->GetFilename()), - wxT("Gambit workbooks (*.gbt)|*.gbt|") wxT("All files (*.*)|*.*"), - wxFD_SAVE | wxFD_OVERWRITE_PROMPT); + wxFileDialog dialog( + this, _("Choose file"), wxPathOnly(m_doc->GetFilename()), + wxFileNameFromPath(m_doc->GetFilename()), + wxT("Gambit workbooks (*.gbt)|*.gbt|") wxT("Gambit extensive games (*.efg)|*.efg|") + wxT("Gambit strategic games (*.nfg)|*.nfg|") wxT("All files (*.*)|*.*"), + wxFD_SAVE | wxFD_OVERWRITE_PROMPT); if (dialog.ShowModal() == wxID_OK) { try { From 02e531e3bbbfc29a73c672b9e2a4218715ba7165 Mon Sep 17 00:00:00 2001 From: Theodore Turocy Date: Wed, 3 Jun 2026 11:06:02 +0100 Subject: [PATCH 2/9] Refactor file saving for DRY (but formats are not yet respected) --- contrib/games/4cards.efg | 21 ++++++++++++++ src/gui/gameframe.cc | 62 +++++++++++++++++++++++++++------------- 2 files changed, 63 insertions(+), 20 deletions(-) diff --git a/contrib/games/4cards.efg b/contrib/games/4cards.efg index 3b42b9416..8843b6c39 100644 --- a/contrib/games/4cards.efg +++ b/contrib/games/4cards.efg @@ -1,3 +1,21 @@ + + + + + + + + + + + + + + + + + + EFG 2 R "4 Card poker, from Alix Martin" { "Player 1" "Player 2" } "" @@ -110,3 +128,6 @@ p "" 1 8 "1p2rJ" { "call" "fold" } 0 t "" 4 "Outcome 4" { -1, 3 } t "" 3 "Outcome 3" { 0, 2 } t "" 3 "Outcome 3" { 0, 2 } + + + diff --git a/src/gui/gameframe.cc b/src/gui/gameframe.cc index d3249a874..e827d27aa 100644 --- a/src/gui/gameframe.cc +++ b/src/gui/gameframe.cc @@ -652,31 +652,53 @@ void GameFrame::OnFileClose(wxCommandEvent &) { Close(); } void GameFrame::OnFileSave(wxCommandEvent &p_event) { - if (p_event.GetId() == wxID_SAVEAS || m_doc->GetFilename().empty()) { - wxFileDialog dialog( - this, _("Choose file"), wxPathOnly(m_doc->GetFilename()), - wxFileNameFromPath(m_doc->GetFilename()), - wxT("Gambit workbooks (*.gbt)|*.gbt|") wxT("Gambit extensive games (*.efg)|*.efg|") - wxT("Gambit strategic games (*.nfg)|*.nfg|") wxT("All files (*.*)|*.*"), - wxFD_SAVE | wxFD_OVERWRITE_PROMPT); - - if (dialog.ShowModal() == wxID_OK) { - try { - m_doc->DoSave(dialog.GetPath()); - } - catch (std::exception &ex) { - ExceptionDialog(this, ex.what()).ShowModal(); - } - } - } - else { + const bool saveAs = p_event.GetId() == wxID_SAVEAS || m_doc->GetFilename().empty(); + + auto doSave = [this](const wxString &path) { try { - m_doc->DoSave(m_doc->GetFilename()); + m_doc->DoSave(path); } - catch (std::exception &ex) { + catch (const std::exception &ex) { ExceptionDialog(this, ex.what()).ShowModal(); } + }; + + if (!saveAs) { + doSave(wxString::FromUTF8(m_doc->GetFilename())); + return; + } + + const wxString currentFilename = wxString::FromUTF8(m_doc->GetFilename()); + + wxFileDialog dialog( + this, _("Save game as"), wxPathOnly(currentFilename), wxFileNameFromPath(currentFilename), + wxT("Gambit workbooks (*.gbt)|*.gbt|") wxT("Gambit extensive games (*.efg)|*.efg|") + wxT("Gambit strategic games (*.nfg)|*.nfg|") wxT("All files (*.*)|*.*"), + wxFD_SAVE | wxFD_OVERWRITE_PROMPT); + + if (dialog.ShowModal() != wxID_OK) { + return; + } + + wxFileName filename(dialog.GetPath()); + + if (!filename.HasExt()) { + switch (dialog.GetFilterIndex()) { + case 0: + filename.SetExt(wxT("gbt")); + break; + case 1: + filename.SetExt(wxT("efg")); + break; + case 2: + filename.SetExt(wxT("nfg")); + break; + default: + break; + } } + + doSave(filename.GetFullPath()); } void GameFrame::OnFilePageSetup(wxCommandEvent &) From 0bbd503321207ee4f5e82a844761b2a1f48be2cd Mon Sep 17 00:00:00 2001 From: Theodore Turocy Date: Wed, 3 Jun 2026 11:31:17 +0100 Subject: [PATCH 3/9] Remove efg/nfg from exports into save file types. --- src/gui/gamedoc.cc | 47 ++++++++++++++++++++++++-------------------- src/gui/gamedoc.h | 15 +++++++++++--- src/gui/gameframe.cc | 47 +++++++++++--------------------------------- src/gui/gameframe.h | 2 -- src/gui/menuconst.h | 2 -- 5 files changed, 49 insertions(+), 64 deletions(-) diff --git a/src/gui/gamedoc.cc b/src/gui/gamedoc.cc index cba37d518..2cde05dea 100644 --- a/src/gui/gamedoc.cc +++ b/src/gui/gamedoc.cc @@ -373,27 +373,32 @@ void GameDocument::SetSelectNode(GameNode p_node) // Commands for model part of MVC architecture start here. //====================================================================== -void GameDocument::DoSave(const wxString &p_filename) -{ - std::ofstream file(static_cast(p_filename.mb_str())); - SaveDocument(file); - m_filename = p_filename; - SetModified(false); - UpdateViews(GBT_DOC_MODIFIED_NONE); -} - -void GameDocument::DoExportEfg(const wxString &p_filename) -{ - std::ofstream file(static_cast(p_filename.mb_str())); - m_game->Write(file, "efg"); - UpdateViews(GBT_DOC_MODIFIED_NONE); -} - -void GameDocument::DoExportNfg(const wxString &p_filename) -{ - std::ofstream file(static_cast(p_filename.mb_str())); - BuildNfg(); - m_game->Write(file, "nfg"); +void GameDocument::DoSave(const wxString &p_filename, GameSaveFormat p_format) +{ + std::ofstream file(p_filename.mb_str()); + if (!file) { + throw std::runtime_error(std::string("Unable to open file for writing: ") + + static_cast(p_filename.mb_str())); + } + switch (p_format) { + case GameSaveFormat::Workbook: + std::cout << "Got format as workbook\n"; + SaveDocument(file); + m_filename = p_filename; + SetModified(false); + break; + + case GameSaveFormat::Efg: + m_game->Write(file, "efg"); + std::cout << "Got format as efg\n"; + break; + + case GameSaveFormat::Nfg: + BuildNfg(); + std::cout << "Got format as nfg\n"; + m_game->Write(file, "nfg"); + break; + } UpdateViews(GBT_DOC_MODIFIED_NONE); } diff --git a/src/gui/gamedoc.h b/src/gui/gamedoc.h index 8218157ea..aa158ff1f 100644 --- a/src/gui/gamedoc.h +++ b/src/gui/gamedoc.h @@ -237,9 +237,18 @@ class GameDocument { void PostPendingChanges(); /// Operations on game model - void DoSave(const wxString &p_filename); - void DoExportEfg(const wxString &p_filename); - void DoExportNfg(const wxString &p_filename); + enum class GameSaveFormat { Efg, Nfg, Workbook }; + GameSaveFormat GetCurrentSaveFormat() const + { + if (m_filename.EndsWith(".efg")) { + return GameSaveFormat::Efg; + } + if (m_filename.EndsWith(".nfg")) { + return GameSaveFormat::Nfg; + } + return GameSaveFormat::Workbook; + } + void DoSave(const wxString &p_filename, GameSaveFormat p_format); void DoSetTitle(const wxString &p_title, const wxString &p_comment); void DoNewPlayer(); void DoSetPlayerLabel(GamePlayer p_player, const wxString &p_label); diff --git a/src/gui/gameframe.cc b/src/gui/gameframe.cc index e827d27aa..ba7bcc2d5 100644 --- a/src/gui/gameframe.cc +++ b/src/gui/gameframe.cc @@ -187,8 +187,6 @@ EVT_MENU(wxID_OPEN, GameFrame::OnFileOpen) EVT_MENU(wxID_CLOSE, GameFrame::OnFileClose) EVT_MENU(wxID_SAVE, GameFrame::OnFileSave) EVT_MENU(wxID_SAVEAS, GameFrame::OnFileSave) -EVT_MENU(GBT_MENU_FILE_EXPORT_EFG, GameFrame::OnFileExportEfg) -EVT_MENU(GBT_MENU_FILE_EXPORT_NFG, GameFrame::OnFileExportNfg) EVT_MENU(GBT_MENU_FILE_EXPORT_BMP, GameFrame::OnFileExportGraphic) EVT_MENU(GBT_MENU_FILE_EXPORT_JPEG, GameFrame::OnFileExportGraphic) EVT_MENU(GBT_MENU_FILE_EXPORT_PNG, GameFrame::OnFileExportGraphic) @@ -322,8 +320,6 @@ void GameFrame::OnUpdate() const GameNode selectNode = m_doc->GetSelectNode(); wxMenuBar *menuBar = GetMenuBar(); - menuBar->Enable(GBT_MENU_FILE_EXPORT_EFG, m_doc->IsTree()); - menuBar->Enable(GBT_MENU_EDIT_INSERT_MOVE, selectNode != nullptr); menuBar->Enable(GBT_MENU_EDIT_INSERT_ACTION, selectNode && selectNode->GetInfoset()); menuBar->Enable(GBT_MENU_EDIT_REVEAL, selectNode && selectNode->GetInfoset()); @@ -418,11 +414,6 @@ void GameFrame::MakeMenus() fileMenu->AppendSeparator(); auto *fileExportMenu = new wxMenu; - fileExportMenu->Append(GBT_MENU_FILE_EXPORT_EFG, _("Gambit .&efg format"), - _("Save the extensive game in .efg format")); - fileExportMenu->Append(GBT_MENU_FILE_EXPORT_NFG, _("Gambit .&nfg format"), - _("Save the strategic game in .nfg format")); - fileExportMenu->AppendSeparator(); fileExportMenu->Append(GBT_MENU_FILE_EXPORT_BMP, _("&BMP"), _("Save a rendering of the game as a Windows bitmap")); fileExportMenu->Append(GBT_MENU_FILE_EXPORT_JPEG, _("&JPEG"), @@ -654,9 +645,9 @@ void GameFrame::OnFileSave(wxCommandEvent &p_event) { const bool saveAs = p_event.GetId() == wxID_SAVEAS || m_doc->GetFilename().empty(); - auto doSave = [this](const wxString &path) { + auto doSave = [this](const wxString &path, GameDocument::GameSaveFormat format) { try { - m_doc->DoSave(path); + m_doc->DoSave(path, format); } catch (const std::exception &ex) { ExceptionDialog(this, ex.what()).ShowModal(); @@ -664,7 +655,7 @@ void GameFrame::OnFileSave(wxCommandEvent &p_event) }; if (!saveAs) { - doSave(wxString::FromUTF8(m_doc->GetFilename())); + doSave(wxString::FromUTF8(m_doc->GetFilename()), m_doc->GetCurrentSaveFormat()); return; } @@ -697,8 +688,14 @@ void GameFrame::OnFileSave(wxCommandEvent &p_event) break; } } - - doSave(filename.GetFullPath()); + GameDocument::GameSaveFormat format = GameDocument::GameSaveFormat::Workbook; + if (filename.GetExt() == wxT("efg")) { + format = GameDocument::GameSaveFormat::Efg; + } + else if (filename.GetExt() == wxT("nfg")) { + format = GameDocument::GameSaveFormat::Nfg; + } + doSave(filename.GetFullPath(), format); } void GameFrame::OnFilePageSetup(wxCommandEvent &) @@ -759,28 +756,6 @@ void GameFrame::OnFilePrint(wxCommandEvent &) } } -void GameFrame::OnFileExportEfg(wxCommandEvent &) -{ - wxFileDialog dialog(this, _("Choose file"), wxGetApp().GetCurrentDir(), _T(""), - wxT("Gambit extensive games (*.efg)|*.efg|") wxT("All files (*.*)|*.*"), - wxFD_SAVE | wxFD_OVERWRITE_PROMPT); - - if (dialog.ShowModal() == wxID_OK) { - m_doc->DoExportEfg(dialog.GetPath()); - } -} - -void GameFrame::OnFileExportNfg(wxCommandEvent &) -{ - wxFileDialog dialog(this, _("Choose file"), wxGetApp().GetCurrentDir(), _T(""), - wxT("Gambit strategic games (*.nfg)|*.nfg|") wxT("All files (*.*)|*.*"), - wxFD_SAVE | wxFD_OVERWRITE_PROMPT); - - if (dialog.ShowModal() == wxID_OK) { - m_doc->DoExportNfg(dialog.GetPath()); - } -} - void GameFrame::OnFileExportGraphic(wxCommandEvent &p_event) { wxBitmap bitmap = wxNullBitmap; diff --git a/src/gui/gameframe.h b/src/gui/gameframe.h index a036a4274..49415be31 100644 --- a/src/gui/gameframe.h +++ b/src/gui/gameframe.h @@ -56,8 +56,6 @@ class GameFrame final : public wxFrame, public GameView { void OnFileOpen(wxCommandEvent &); void OnFileClose(wxCommandEvent &); void OnFileSave(wxCommandEvent &); - void OnFileExportEfg(wxCommandEvent &); - void OnFileExportNfg(wxCommandEvent &); void OnFileExportGraphic(wxCommandEvent &); void OnFileExportPS(wxCommandEvent &); void OnFileExportSVG(wxCommandEvent &); diff --git a/src/gui/menuconst.h b/src/gui/menuconst.h index 2263a7381..c52bdf9f9 100644 --- a/src/gui/menuconst.h +++ b/src/gui/menuconst.h @@ -33,8 +33,6 @@ enum MenuItems { GBT_MENU_FILE_EXPORT_PNG = 1105, GBT_MENU_FILE_EXPORT_POSTSCRIPT = 1106, GBT_MENU_FILE_EXPORT_SVG = 1109, - GBT_MENU_FILE_EXPORT_EFG = 1107, - GBT_MENU_FILE_EXPORT_NFG = 1108, GBT_MENU_EDIT_INSERT_MOVE = 1200, GBT_MENU_EDIT_INSERT_ACTION = 1201, From 75a5210bd2f3a11e2ba399f98b8c8846edeb625f Mon Sep 17 00:00:00 2001 From: Theodore Turocy Date: Wed, 3 Jun 2026 11:39:26 +0100 Subject: [PATCH 4/9] Remove debugging output --- src/gui/gamedoc.cc | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/gui/gamedoc.cc b/src/gui/gamedoc.cc index 2cde05dea..3636bae43 100644 --- a/src/gui/gamedoc.cc +++ b/src/gui/gamedoc.cc @@ -382,7 +382,6 @@ void GameDocument::DoSave(const wxString &p_filename, GameSaveFormat p_format) } switch (p_format) { case GameSaveFormat::Workbook: - std::cout << "Got format as workbook\n"; SaveDocument(file); m_filename = p_filename; SetModified(false); @@ -390,12 +389,10 @@ void GameDocument::DoSave(const wxString &p_filename, GameSaveFormat p_format) case GameSaveFormat::Efg: m_game->Write(file, "efg"); - std::cout << "Got format as efg\n"; break; case GameSaveFormat::Nfg: BuildNfg(); - std::cout << "Got format as nfg\n"; m_game->Write(file, "nfg"); break; } From 162d7a45c62a7457cd74283d43c6c8d053ff0c84 Mon Sep 17 00:00:00 2001 From: Theodore Turocy Date: Wed, 3 Jun 2026 11:40:00 +0100 Subject: [PATCH 5/9] Revert accidental change to game file --- contrib/games/4cards.efg | 21 --------------------- 1 file changed, 21 deletions(-) diff --git a/contrib/games/4cards.efg b/contrib/games/4cards.efg index 8843b6c39..3b42b9416 100644 --- a/contrib/games/4cards.efg +++ b/contrib/games/4cards.efg @@ -1,21 +1,3 @@ - - - - - - - - - - - - - - - - - - EFG 2 R "4 Card poker, from Alix Martin" { "Player 1" "Player 2" } "" @@ -128,6 +110,3 @@ p "" 1 8 "1p2rJ" { "call" "fold" } 0 t "" 4 "Outcome 4" { -1, 3 } t "" 3 "Outcome 3" { 0, 2 } t "" 3 "Outcome 3" { 0, 2 } - - - From a7f35f5bc37d3e7aa0f8ee43c0d3fe0b641133a8 Mon Sep 17 00:00:00 2001 From: Theodore Turocy Date: Wed, 3 Jun 2026 11:56:48 +0100 Subject: [PATCH 6/9] Separate game modification from unsaved analysis --- src/gui/gamedoc.cc | 19 ++++++++++--------- src/gui/gamedoc.h | 7 ++++--- 2 files changed, 14 insertions(+), 12 deletions(-) diff --git a/src/gui/gamedoc.cc b/src/gui/gamedoc.cc index 3636bae43..4d7d5b8d7 100644 --- a/src/gui/gamedoc.cc +++ b/src/gui/gamedoc.cc @@ -99,8 +99,8 @@ bool StrategyDominanceStack::PreviousLevel() //========================================================================= GameDocument::GameDocument(Game p_game) - : m_game(p_game), m_selectNode(nullptr), m_modified(false), m_stratSupports(this, true), - m_currentProfileList(0) + : m_game(p_game), m_selectNode(nullptr), m_gameModified(false), m_unsavedResults(false), + m_stratSupports(this, true), m_currentProfileList(0) { wxGetApp().AddDocument(this); @@ -112,7 +112,7 @@ GameDocument::~GameDocument() { wxGetApp().RemoveDocument(this); } bool GameDocument::LoadDocument(const wxString &p_filename) { - TiXmlDocument doc((const char *)p_filename.mb_str()); + TiXmlDocument doc(p_filename.mb_str()); if (!doc.LoadFile()) { // Some error occurred. Do something smart later. return false; @@ -259,12 +259,10 @@ void GameDocument::SaveDocument(std::ostream &p_file) const void GameDocument::UpdateViews(GameModificationType p_modifications) { - if (p_modifications != GBT_DOC_MODIFIED_NONE) { - m_modified = true; - std::ostringstream s; - SaveDocument(s); + if (p_modifications == GBT_DOC_MODIFIED_GAME || p_modifications == GBT_DOC_MODIFIED_PAYOFFS || + p_modifications == GBT_DOC_MODIFIED_LABELS) { + m_gameModified = true; } - if (p_modifications == GBT_DOC_MODIFIED_GAME || p_modifications == GBT_DOC_MODIFIED_PAYOFFS) { m_stratSupports.Reset(); @@ -384,16 +382,19 @@ void GameDocument::DoSave(const wxString &p_filename, GameSaveFormat p_format) case GameSaveFormat::Workbook: SaveDocument(file); m_filename = p_filename; - SetModified(false); + m_gameModified = false; + m_unsavedResults = false; break; case GameSaveFormat::Efg: m_game->Write(file, "efg"); + m_gameModified = false; break; case GameSaveFormat::Nfg: BuildNfg(); m_game->Write(file, "nfg"); + m_gameModified = false; break; } UpdateViews(GBT_DOC_MODIFIED_NONE); diff --git a/src/gui/gamedoc.h b/src/gui/gamedoc.h index 574a0979d..314e8a4fe 100644 --- a/src/gui/gamedoc.h +++ b/src/gui/gamedoc.h @@ -152,7 +152,7 @@ class GameDocument { TreeRenderConfig m_style; GameNode m_selectNode; - bool m_modified; + bool m_gameModified, m_unsavedResults; StrategyDominanceStack m_stratSupports; @@ -181,8 +181,9 @@ class GameDocument { const wxString &GetFilename() const { return m_filename; } void SetFilename(const wxString &p_filename) { m_filename = p_filename; } - bool IsModified() const { return m_modified; } - void SetModified(bool p_modified) { m_modified = p_modified; } + bool IsModified() const { return m_gameModified || m_unsavedResults; } + void SetGameModified(bool p_modified) { m_gameModified = p_modified; } + void SetUnsavedResults(bool p_unsaved) { m_unsavedResults = p_unsaved; } const TreeRenderConfig &GetStyle() const { return m_style; } void SetStyle(const TreeRenderConfig &p_style); From 78ee1941d80beb4be0f526af322ec949725d9b63 Mon Sep 17 00:00:00 2001 From: Theodore Turocy Date: Wed, 3 Jun 2026 12:08:39 +0100 Subject: [PATCH 7/9] Some improvement to close warning message --- src/gui/gamedoc.h | 2 ++ src/gui/gameframe.cc | 38 ++++++++++++++++++++++++++++++++++---- 2 files changed, 36 insertions(+), 4 deletions(-) diff --git a/src/gui/gamedoc.h b/src/gui/gamedoc.h index 314e8a4fe..32f2e9e1e 100644 --- a/src/gui/gamedoc.h +++ b/src/gui/gamedoc.h @@ -182,6 +182,8 @@ class GameDocument { void SetFilename(const wxString &p_filename) { m_filename = p_filename; } bool IsModified() const { return m_gameModified || m_unsavedResults; } + bool IsGameModified() const { return m_gameModified; } + bool AreResultsUnsaved() const { return m_unsavedResults; } void SetGameModified(bool p_modified) { m_gameModified = p_modified; } void SetUnsavedResults(bool p_unsaved) { m_unsavedResults = p_unsaved; } diff --git a/src/gui/gameframe.cc b/src/gui/gameframe.cc index d741cf039..d5c28286d 100644 --- a/src/gui/gameframe.cc +++ b/src/gui/gameframe.cc @@ -1274,16 +1274,46 @@ void GameFrame::OnUnsplit(wxSplitterEvent &) GetToolBar()->ToggleTool(GBT_MENU_VIEW_PROFILES, false); } +namespace { + +wxString CloseWarningMessage(GameDocument *p_doc) +{ + if (p_doc->IsGameModified() && !p_doc->AreResultsUnsaved()) { + return _("This game has unsaved changes.\n\n" + "Close without saving?"); + } + if (!p_doc->IsGameModified() && p_doc->AreResultsUnsaved()) { + return _("There are unsaved computational results.\n\n" + "These changes are not saved in ordinary game files.\n" + "Close without saving?"); + } + if (p_doc->IsGameModified() && p_doc->AreResultsUnsaved()) { + return _("This game has unsaved changes, and there are unsaved computational results " + "unsaved computational results or workspace changes.\n\n" + "Close without saving?"); + } + return wxEmptyString; +} + +} // namespace + void GameFrame::OnCloseWindow(wxCloseEvent &p_event) { - if (p_event.CanVeto() && m_doc->IsModified()) { - if (wxMessageBox(wxT("Game has been modified.\n") wxT("Unsaved changes will be lost!\n") - wxT("Close anyway?"), - _("Warning"), wxOK | wxCANCEL) == wxCANCEL) { + if (!p_event.CanVeto()) { + p_event.Skip(); + return; + } + + if (m_doc->IsModified()) { + const int response = wxMessageBox(CloseWarningMessage(m_doc), _("Unsaved Changes"), + wxYES_NO | wxNO_DEFAULT | wxICON_WARNING, this); + + if (response != wxYES) { p_event.Veto(); return; } } + p_event.Skip(); } From fcdd0c5adcd44e05f869ff8167a00834b5aed644 Mon Sep 17 00:00:00 2001 From: Theodore Turocy Date: Wed, 3 Jun 2026 12:29:49 +0100 Subject: [PATCH 8/9] Update ChangeLog --- ChangeLog | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ChangeLog b/ChangeLog index e916d8aa6..a50e9f412 100644 --- a/ChangeLog +++ b/ChangeLog @@ -23,6 +23,8 @@ - Added a new welcome/landing window on launching the GUI without a game. This has the effect of avoiding creating a window with a trivial extensive game which the user then has to dismiss if they do not require it. (#80) +- Clarified handling of .efg/.nfg/.gbt file types in graphical interface and refined warnings about + unsaved work. (#12) ### Removed - Built-in plotting of logit QRE for strategic games has been removed in the GUI (#809) From 62b93b543343f485e7acc73807b2fecc9cab16c3 Mon Sep 17 00:00:00 2001 From: Theodore Turocy Date: Wed, 3 Jun 2026 12:36:45 +0100 Subject: [PATCH 9/9] Add missing include --- src/gui/gameframe.cc | 1 + 1 file changed, 1 insertion(+) diff --git a/src/gui/gameframe.cc b/src/gui/gameframe.cc index d5c28286d..dbd4d61da 100644 --- a/src/gui/gameframe.cc +++ b/src/gui/gameframe.cc @@ -27,6 +27,7 @@ #ifndef WX_PRECOMP #include #endif // WX_PRECOMP +#include #include #include #if !defined(__WXMSW__) || wxUSE_POSTSCRIPT