Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions ChangeLog
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
2 changes: 1 addition & 1 deletion src/gui/app.cc
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,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;
}
Expand Down
55 changes: 29 additions & 26 deletions src/gui/gamedoc.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand All @@ -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;
Expand Down Expand Up @@ -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();

Expand Down Expand Up @@ -373,27 +371,32 @@ void GameDocument::SetSelectNode(GameNode p_node)
// Commands for model part of MVC architecture start here.
//======================================================================

void GameDocument::DoSave(const wxString &p_filename)
void GameDocument::DoSave(const wxString &p_filename, GameSaveFormat p_format)
{
std::ofstream file(static_cast<const char *>(p_filename.mb_str()));
SaveDocument(file);
m_filename = p_filename;
SetModified(false);
UpdateViews(GBT_DOC_MODIFIED_NONE);
}
std::ofstream file(p_filename.mb_str());
if (!file) {
throw std::runtime_error(std::string("Unable to open file for writing: ") +
static_cast<const char *>(p_filename.mb_str()));
}
switch (p_format) {
case GameSaveFormat::Workbook:
SaveDocument(file);
m_filename = p_filename;
m_gameModified = false;
m_unsavedResults = false;
break;

void GameDocument::DoExportEfg(const wxString &p_filename)
{
std::ofstream file(static_cast<const char *>(p_filename.mb_str()));
m_game->Write(file, "efg");
UpdateViews(GBT_DOC_MODIFIED_NONE);
}
case GameSaveFormat::Efg:
m_game->Write(file, "efg");
m_gameModified = false;
break;

void GameDocument::DoExportNfg(const wxString &p_filename)
{
std::ofstream file(static_cast<const char *>(p_filename.mb_str()));
BuildNfg();
m_game->Write(file, "nfg");
case GameSaveFormat::Nfg:
BuildNfg();
m_game->Write(file, "nfg");
m_gameModified = false;
break;
}
UpdateViews(GBT_DOC_MODIFIED_NONE);
}

Expand Down
24 changes: 18 additions & 6 deletions src/gui/gamedoc.h
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@ class GameDocument {

TreeRenderConfig m_style;
GameNode m_selectNode;
bool m_modified;
bool m_gameModified, m_unsavedResults;

StrategyDominanceStack m_stratSupports;

Expand Down Expand Up @@ -181,8 +181,11 @@ 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; }
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; }

const TreeRenderConfig &GetStyle() const { return m_style; }
void SetStyle(const TreeRenderConfig &p_style);
Expand Down Expand Up @@ -237,9 +240,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);
Expand Down
136 changes: 83 additions & 53 deletions src/gui/gameframe.cc
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
#ifndef WX_PRECOMP
#include <wx/wx.h>
#endif // WX_PRECOMP
#include <wx/filename.h>
#include <wx/fontdlg.h>
#include <wx/printdlg.h>
#if !defined(__WXMSW__) || wxUSE_POSTSCRIPT
Expand Down Expand Up @@ -188,8 +189,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)
Expand Down Expand Up @@ -323,8 +322,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());
Expand Down Expand Up @@ -419,11 +416,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"),
Expand Down Expand Up @@ -641,29 +633,59 @@ 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);

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, GameDocument::GameSaveFormat format) {
try {
m_doc->DoSave(m_doc->GetFilename());
m_doc->DoSave(path, format);
}
catch (std::exception &ex) {
catch (const std::exception &ex) {
ExceptionDialog(this, ex.what()).ShowModal();
}
};

if (!saveAs) {
doSave(wxString::FromUTF8(m_doc->GetFilename()), m_doc->GetCurrentSaveFormat());
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;
}
}
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 &)
Expand Down Expand Up @@ -724,28 +746,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;
Expand Down Expand Up @@ -1275,16 +1275,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();
}

Expand Down
2 changes: 0 additions & 2 deletions src/gui/gameframe.h
Original file line number Diff line number Diff line change
Expand Up @@ -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 &);
Expand Down
2 changes: 0 additions & 2 deletions src/gui/menuconst.h
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
Loading