// // $Source: /cvsroot/gambit/gambit/sources/gui/dlefglogit.cc,v $ // $Date: 2006/01/13 20:56:24 $ // $Revision: 1.12 $ // // DESCRIPTION: // Dialog for monitoring progress of logit equilibrium computation // // This file is part of Gambit // Copyright (c) 2005, The Gambit Project // // This program is free software; you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation; either version 2 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software // Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. // #include #include #ifndef WX_PRECOMP #include #endif // WX_PRECOMP #include #include #include "wx/sheet/sheet.h" #include "dlefglogit.h" class gbtLogitBehavList : public wxSheet { private: gbtGameDocument *m_doc; Gambit::List m_lambdas; Gambit::List > m_profiles; // Overriding wxSheet members for data access wxString GetCellValue(const wxSheetCoords &); wxSheetCellAttr GetAttr(const wxSheetCoords &p_coords, wxSheetAttr_Type) const; // Overriding wxSheet members to disable selection behavior bool SelectRow(int, bool = false, bool = false) { return false; } bool SelectRows(int, int, bool = false, bool = false) { return false; } bool SelectCol(int, bool = false, bool = false) { return false; } bool SelectCols(int, int, bool = false, bool = false) { return false; } bool SelectCell(const wxSheetCoords&, bool = false, bool = false) { return false; } bool SelectBlock(const wxSheetBlock&, bool = false, bool = false) { return false; } bool SelectAll(bool = false) { return false; } // Overriding wxSheet member to suppress drawing of cursor void DrawCursorCellHighlight(wxDC&, const wxSheetCellAttr &) { } public: gbtLogitBehavList(wxWindow *p_parent, gbtGameDocument *p_doc); virtual ~gbtLogitBehavList(); void AddProfile(const wxString &p_text, bool p_forceShow); }; gbtLogitBehavList::gbtLogitBehavList(wxWindow *p_parent, gbtGameDocument *p_doc) : wxSheet(p_parent, -1), m_doc(p_doc) { CreateGrid(0, 0); SetRowLabelWidth(40); SetColLabelHeight(25); } gbtLogitBehavList::~gbtLogitBehavList() { } wxString gbtLogitBehavList::GetCellValue(const wxSheetCoords &p_coords) { if (IsRowLabelCell(p_coords)) { return wxString::Format(wxT("%d"), p_coords.GetRow() + 1); } else if (IsColLabelCell(p_coords)) { if (p_coords.GetCol() == 0) { return wxT("Lambda"); } else { Gambit::GameAction action = m_doc->GetGame()->GetAction(p_coords.GetCol()); return (wxString::Format(wxT("%d: "), action->GetInfoset()->GetNumber())+ wxString(action->GetLabel().c_str(), *wxConvCurrent)); } } else if (IsCornerLabelCell(p_coords)) { return wxT("#"); } if (p_coords.GetCol() == 0) { return wxString(Gambit::ToText(m_lambdas[p_coords.GetRow()+1], m_doc->GetStyle().NumDecimals()).c_str(), *wxConvCurrent); } else { const Gambit::MixedBehavProfile &profile = m_profiles[p_coords.GetRow()+1]; return wxString(Gambit::ToText(profile[p_coords.GetCol()], m_doc->GetStyle().NumDecimals()).c_str(), *wxConvCurrent); } } static wxColour GetPlayerColor(gbtGameDocument *p_doc, int p_index) { if (p_index == 0) return *wxBLACK; Gambit::GameAction action = p_doc->GetGame()->GetAction(p_index); return p_doc->GetStyle().GetPlayerColor(action->GetInfoset()->GetPlayer()->GetNumber()); } wxSheetCellAttr gbtLogitBehavList::GetAttr(const wxSheetCoords &p_coords, wxSheetAttr_Type) const { if (IsRowLabelCell(p_coords)) { wxSheetCellAttr attr(GetSheetRefData()->m_defaultRowLabelAttr); attr.SetFont(wxFont(10, wxSWISS, wxNORMAL, wxBOLD)); attr.SetAlignment(wxALIGN_CENTER, wxALIGN_CENTER); attr.SetOrientation(wxHORIZONTAL); attr.SetReadOnly(true); return attr; } else if (IsColLabelCell(p_coords)) { wxSheetCellAttr attr(GetSheetRefData()->m_defaultColLabelAttr); attr.SetFont(wxFont(10, wxSWISS, wxNORMAL, wxBOLD)); attr.SetAlignment(wxALIGN_CENTER, wxALIGN_CENTER); attr.SetOrientation(wxHORIZONTAL); attr.SetReadOnly(true); attr.SetForegroundColour(GetPlayerColor(m_doc, p_coords.GetCol())); return attr; } else if (IsCornerLabelCell(p_coords)) { return GetSheetRefData()->m_defaultCornerLabelAttr; } wxSheetCellAttr attr(GetSheetRefData()->m_defaultGridCellAttr); attr.SetFont(wxFont(10, wxSWISS, wxNORMAL, wxNORMAL)); attr.SetAlignment(wxALIGN_RIGHT, wxALIGN_CENTER); attr.SetOrientation(wxHORIZONTAL); if (p_coords.GetCol() > 0) { Gambit::GameAction action = m_doc->GetGame()->GetAction(p_coords.GetCol()); attr.SetForegroundColour(m_doc->GetStyle().GetPlayerColor(action->GetInfoset()->GetPlayer()->GetNumber())); if (action->GetInfoset()->GetNumber() % 2 == 0) { attr.SetBackgroundColour(wxColour(250, 250, 250)); } else { attr.SetBackgroundColour(wxColour(225, 225, 225)); } } else { attr.SetForegroundColour(*wxBLACK); attr.SetBackgroundColour(wxColour(250, 250, 250)); } attr.SetReadOnly(true); return attr; } void gbtLogitBehavList::AddProfile(const wxString &p_text, bool p_forceShow) { if (GetNumberCols() == 0) { AppendCols(m_doc->GetGame()->BehavProfileLength() + 1); } Gambit::MixedBehavProfile profile(m_doc->GetGame()); wxStringTokenizer tok(p_text, wxT(",")); m_lambdas.Append((double) Gambit::ToNumber(std::string((const char *) tok.GetNextToken().mb_str()))); for (int i = 1; i <= profile.Length(); i++) { profile[i] = Gambit::ToNumber(std::string((const char *) tok.GetNextToken().mb_str())); } m_profiles.Append(profile); if (p_forceShow || m_profiles.Length() - GetNumberRows() > 20) { AppendRows(m_profiles.Length() - GetNumberRows()); MakeCellVisible(wxSheetCoords(GetNumberRows() - 1, 0)); } // Lambda tends to get large, so this column usually needs resized AutoSizeCol(0); } const int GBT_ID_TIMER = 1000; const int GBT_ID_PROCESS = 1001; BEGIN_EVENT_TABLE(gbtLogitBehavDialog, wxDialog) EVT_END_PROCESS(GBT_ID_PROCESS, gbtLogitBehavDialog::OnEndProcess) EVT_IDLE(gbtLogitBehavDialog::OnIdle) EVT_TIMER(GBT_ID_TIMER, gbtLogitBehavDialog::OnTimer) EVT_BUTTON(wxID_SAVE, gbtLogitBehavDialog::OnSave) END_EVENT_TABLE() #include "bitmaps/stop.xpm" gbtLogitBehavDialog::gbtLogitBehavDialog(wxWindow *p_parent, gbtGameDocument *p_doc) : wxDialog(p_parent, -1, wxT("Compute quantal response equilibria"), wxDefaultPosition), m_doc(p_doc), m_process(0), m_timer(this, GBT_ID_TIMER) { wxBoxSizer *sizer = new wxBoxSizer(wxVERTICAL); wxBoxSizer *startSizer = new wxBoxSizer(wxHORIZONTAL); m_statusText = new wxStaticText(this, wxID_STATIC, wxT("The computation is currently in progress.")); m_statusText->SetForegroundColour(*wxBLUE); startSizer->Add(m_statusText, 0, wxALL | wxALIGN_CENTER, 5); m_stopButton = new wxBitmapButton(this, wxID_CANCEL, wxBitmap(stop_xpm)); m_stopButton->SetToolTip(_("Stop the computation")); startSizer->Add(m_stopButton, 0, wxALL | wxALIGN_CENTER, 5); Connect(wxID_CANCEL, wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(gbtLogitBehavDialog::OnStop)); sizer->Add(startSizer, 0, wxALL | wxALIGN_CENTER, 5); m_behavList = new gbtLogitBehavList(this, m_doc); m_behavList->SetSizeHints(wxSize(600, 400)); sizer->Add(m_behavList, 0, wxALL | wxALIGN_CENTER, 5); wxBoxSizer *buttonSizer = new wxBoxSizer(wxHORIZONTAL); m_saveButton = new wxButton(this, wxID_SAVE, wxT("Save correspondence to .csv file")); m_saveButton->Enable(false); buttonSizer->Add(m_saveButton, 0, wxALL | wxALIGN_CENTER, 5); m_okButton = new wxButton(this, wxID_OK, wxT("OK")); buttonSizer->Add(m_okButton, 0, wxALL | wxALIGN_CENTER, 5); m_okButton->Enable(false); sizer->Add(buttonSizer, 0, wxALL | wxALIGN_RIGHT, 5); SetSizer(sizer); sizer->Fit(this); sizer->SetSizeHints(this); Layout(); CenterOnParent(); Start(); } void gbtLogitBehavDialog::Start(void) { m_process = new wxProcess(this, GBT_ID_PROCESS); m_process->Redirect(); m_pid = wxExecute(wxT("gambit-logit"), wxEXEC_ASYNC, m_process); std::ostringstream s; m_doc->GetGame()->WriteEfgFile(s); wxString str(wxString(s.str().c_str(), *wxConvCurrent)); // It is possible that the whole string won't write on one go, so // we should take this possibility into account. If the write doesn't // complete the whole way, we take a 100-millisecond siesta and try // again. (This seems to primarily be an issue with -- you guessed it -- // Windows!) while (str.length() > 0) { wxTextOutputStream os(*m_process->GetOutputStream()); // It appears that (at least with mingw) the string itself contains // only '\n' for newlines. If we don't SetMode here, these get // converted to '\r\n' sequences, and so the number of characters // LastWrite() returns does not match the number of characters in // our string. Setting this explicitly solves this problem. os.SetMode(wxEOL_UNIX); os.WriteString(str); str.Remove(0, m_process->GetOutputStream()->LastWrite()); wxMilliSleep(100); } m_process->CloseOutput(); m_timer.Start(1000, false); } void gbtLogitBehavDialog::OnIdle(wxIdleEvent &p_event) { if (!m_process) return; if (m_process->IsInputAvailable()) { wxTextInputStream tis(*m_process->GetInputStream()); wxString msg; msg << tis.ReadLine(); m_behavList->AddProfile(msg, false); m_output += msg; m_output += wxT("\n"); p_event.RequestMore(); } else { m_timer.Start(1000, false); } } void gbtLogitBehavDialog::OnTimer(wxTimerEvent &p_event) { wxWakeUpIdle(); } void gbtLogitBehavDialog::OnEndProcess(wxProcessEvent &p_event) { m_stopButton->Enable(false); m_timer.Stop(); while (m_process->IsInputAvailable()) { wxTextInputStream tis(*m_process->GetInputStream()); wxString msg; msg << tis.ReadLine(); if (msg != wxT("")) { m_behavList->AddProfile(msg, true); m_output += msg; m_output += wxT("\n"); } } if (p_event.GetExitCode() == 0) { m_statusText->SetLabel(wxT("The computation has completed.")); m_statusText->SetForegroundColour(wxColour(0, 192, 0)); } else { m_statusText->SetLabel(wxT("The computation ended abnormally.")); m_statusText->SetForegroundColour(*wxRED); } m_okButton->Enable(true); m_saveButton->Enable(true); } void gbtLogitBehavDialog::OnStop(wxCommandEvent &) { // Per the wxWidgets wiki, under Windows, programs that run // without a console window don't respond to the more polite // SIGTERM, so instead we must be rude and SIGKILL it. m_stopButton->Enable(false); #ifdef __WXMSW__ wxProcess::Kill(m_pid, wxSIGKILL); #else wxProcess::Kill(m_pid, wxSIGTERM); #endif // __WXMSW__ } void gbtLogitBehavDialog::OnSave(wxCommandEvent &) { wxFileDialog dialog(this, _("Choose file"), wxT(""), wxT(""), wxT("CSV files (*.csv)|*.csv|" "All files (*.*)|*.*"), wxSAVE | wxOVERWRITE_PROMPT); if (dialog.ShowModal() == wxID_OK) { std::ofstream file((const char *) dialog.GetPath().mb_str()); file << ((const char *) m_output.mb_str()); } }