1 //
2 // This file is part of Gambit
3 // Copyright (c) 1994-2016, The Gambit Project (http://www.gambit-project.org)
4 //
5 // FILE: src/gui/dlnfglogit.cc
6 // Dialog for monitoring progress of logit equilibrium computation
7 //
8 // This program is free software; you can redistribute it and/or modify
9 // it under the terms of the GNU General Public License as published by
10 // the Free Software Foundation; either version 2 of the License, or
11 // (at your option) any later version.
12 //
13 // This program is distributed in the hope that it will be useful,
14 // but WITHOUT ANY WARRANTY; without even the implied warranty of
15 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 // GNU General Public License for more details.
17 //
18 // You should have received a copy of the GNU General Public License
19 // along with this program; if not, write to the Free Software
20 // Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
21 //
22
23 #include <fstream>
24
25 #include <wx/wxprec.h>
26 #ifndef WX_PRECOMP
27 #include <wx/wx.h>
28 #endif // WX_PRECOMP
29 #include <wx/stdpaths.h>
30 #include <wx/txtstrm.h>
31 #include <wx/tokenzr.h>
32 #include <wx/process.h>
33 #include <wx/print.h>
34
35 #include "wx/sheet/sheet.h"
36 #include "wx/plotctrl/plotctrl.h"
37 #include "wx/wxthings/spinctld.h" // for wxSpinCtrlDbl
38
39 #include "gamedoc.h"
40 #include "menuconst.h" // for tool IDs
41
42 using namespace Gambit;
43
44
45 // Use an anonymous namespace to encapsulate the helper classes
46
47 namespace {
48
49 //========================================================================
50 // class LogitMixedBranch
51 //========================================================================
52
53 /// Represents one branch of a logit equilibrium correspondence
54 class LogitMixedBranch {
55 private:
56 gbtGameDocument *m_doc;
57 List<double> m_lambdas;
58 List<MixedStrategyProfile<double> > m_profiles;
59
60 public:
LogitMixedBranch(gbtGameDocument * p_doc)61 LogitMixedBranch(gbtGameDocument *p_doc) : m_doc(p_doc) { }
62
63 void AddProfile(const wxString &p_text);
64
NumPoints(void) const65 int NumPoints(void) const { return m_lambdas.Length(); }
GetLambda(int p_index) const66 double GetLambda(int p_index) const { return m_lambdas[p_index]; }
GetLambdas(void) const67 const List<double> &GetLambdas(void) const { return m_lambdas; }
GetProfile(int p_index)68 const MixedStrategyProfile<double> &GetProfile(int p_index)
69 { return m_profiles[p_index]; }
GetProfiles(void) const70 const List<MixedStrategyProfile<double> > &GetProfiles(void) const
71 { return m_profiles; }
72 };
73
AddProfile(const wxString & p_text)74 void LogitMixedBranch::AddProfile(const wxString &p_text)
75 {
76 MixedStrategyProfile<double> profile(m_doc->GetGame()->NewMixedStrategyProfile(0.0));
77
78 wxStringTokenizer tok(p_text, wxT(","));
79
80 m_lambdas.Append((double) lexical_cast<Rational>(std::string((const char *) tok.GetNextToken().mb_str())));
81
82 for (int i = 1; i <= profile.MixedProfileLength(); i++) {
83 profile[i] = lexical_cast<Rational>(std::string((const char *) tok.GetNextToken().mb_str()));
84 }
85
86 m_profiles.Append(profile);
87 }
88
89 //========================================================================
90 // class LogitMixedSheet
91 //========================================================================
92
93 class LogitMixedSheet : public wxSheet {
94 private:
95 gbtGameDocument *m_doc;
96 LogitMixedBranch &m_branch;
97
98 // Overriding wxSheet members for data access
99 wxString GetCellValue(const wxSheetCoords &);
100 wxSheetCellAttr GetAttr(const wxSheetCoords &p_coords, wxSheetAttr_Type) const;
101
102 // Overriding wxSheet members to disable selection behavior
SelectRow(int,bool=false,bool=false)103 bool SelectRow(int, bool = false, bool = false)
104 { return false; }
SelectRows(int,int,bool=false,bool=false)105 bool SelectRows(int, int, bool = false, bool = false)
106 { return false; }
SelectCol(int,bool=false,bool=false)107 bool SelectCol(int, bool = false, bool = false)
108 { return false; }
SelectCols(int,int,bool=false,bool=false)109 bool SelectCols(int, int, bool = false, bool = false)
110 { return false; }
SelectCell(const wxSheetCoords &,bool=false,bool=false)111 bool SelectCell(const wxSheetCoords&, bool = false, bool = false)
112 { return false; }
SelectBlock(const wxSheetBlock &,bool=false,bool=false)113 bool SelectBlock(const wxSheetBlock&, bool = false, bool = false)
114 { return false; }
SelectAll(bool=false)115 bool SelectAll(bool = false) { return false; }
116
117 // Overriding wxSheet member to suppress drawing of cursor
DrawCursorCellHighlight(wxDC &,const wxSheetCellAttr &)118 void DrawCursorCellHighlight(wxDC&, const wxSheetCellAttr &) { }
119
120 public:
121 LogitMixedSheet(wxWindow *p_parent, gbtGameDocument *p_doc,
122 LogitMixedBranch &p_branch);
123 virtual ~LogitMixedSheet();
124 };
125
LogitMixedSheet(wxWindow * p_parent,gbtGameDocument * p_doc,LogitMixedBranch & p_branch)126 LogitMixedSheet::LogitMixedSheet(wxWindow *p_parent,
127 gbtGameDocument *p_doc,
128 LogitMixedBranch &p_branch)
129 : wxSheet(p_parent, -1), m_doc(p_doc), m_branch(p_branch)
130 {
131 CreateGrid(p_branch.NumPoints(), p_doc->GetGame()->MixedProfileLength()+1);
132 SetRowLabelWidth(40);
133 SetColLabelHeight(25);
134 }
135
~LogitMixedSheet()136 LogitMixedSheet::~LogitMixedSheet()
137 { }
138
GetCellValue(const wxSheetCoords & p_coords)139 wxString LogitMixedSheet::GetCellValue(const wxSheetCoords &p_coords)
140 {
141 if (!m_doc->GetGame()) return wxT("");
142
143 if (IsRowLabelCell(p_coords)) {
144 return wxString::Format(wxT("%d"), p_coords.GetRow() + 1);
145 }
146 else if (IsColLabelCell(p_coords)) {
147 if (p_coords.GetCol() == 0) {
148 return wxT("Lambda");
149 }
150 else {
151 int index = 1;
152 for (int pl = 1; pl <= m_doc->GetGame()->NumPlayers(); pl++) {
153 GamePlayer player = m_doc->GetGame()->GetPlayer(pl);
154 for (int st = 1; st <= player->NumStrategies(); st++) {
155 if (index++ == p_coords.GetCol()) {
156 return (wxString::Format(wxT("%d: "), pl) +
157 wxString(player->GetStrategy(st)->GetLabel().c_str(),
158 *wxConvCurrent));
159 }
160 }
161 }
162 return wxT("");
163 }
164 }
165 else if (IsCornerLabelCell(p_coords)) {
166 return wxT("#");
167 }
168
169 if (p_coords.GetCol() == 0) {
170 return wxString(lexical_cast<std::string>(m_branch.GetLambda(p_coords.GetRow()+1),
171 m_doc->GetStyle().NumDecimals()).c_str(),
172 *wxConvCurrent);
173 }
174 else {
175 const MixedStrategyProfile<double> &profile =
176 m_branch.GetProfile(p_coords.GetRow()+1);
177 return wxString(lexical_cast<std::string>(profile[p_coords.GetCol()],
178 m_doc->GetStyle().NumDecimals()).c_str(),
179 *wxConvCurrent);
180 }
181 }
182
183
GetPlayerColor(gbtGameDocument * p_doc,int p_index)184 static wxColour GetPlayerColor(gbtGameDocument *p_doc, int p_index)
185 {
186 if (!p_doc->GetGame()) return *wxBLACK;
187
188 int index = 1;
189 for (int pl = 1; pl <= p_doc->GetGame()->NumPlayers(); pl++) {
190 GamePlayer player = p_doc->GetGame()->GetPlayer(pl);
191 for (int st = 1; st <= player->NumStrategies(); st++) {
192 if (index++ == p_index) {
193 return p_doc->GetStyle().GetPlayerColor(pl);
194 }
195 }
196 }
197 return *wxBLACK;
198 }
199
GetAttr(const wxSheetCoords & p_coords,wxSheetAttr_Type) const200 wxSheetCellAttr LogitMixedSheet::GetAttr(const wxSheetCoords &p_coords,
201 wxSheetAttr_Type) const
202 {
203 if (IsRowLabelCell(p_coords)) {
204 wxSheetCellAttr attr(GetSheetRefData()->m_defaultRowLabelAttr);
205 attr.SetFont(wxFont(10, wxSWISS, wxNORMAL, wxBOLD));
206 attr.SetAlignment(wxALIGN_CENTER, wxALIGN_CENTER);
207 attr.SetOrientation(wxHORIZONTAL);
208 attr.SetReadOnly(true);
209 return attr;
210 }
211 else if (IsColLabelCell(p_coords)) {
212 wxSheetCellAttr attr(GetSheetRefData()->m_defaultColLabelAttr);
213 attr.SetFont(wxFont(10, wxSWISS, wxNORMAL, wxBOLD));
214 attr.SetAlignment(wxALIGN_CENTER, wxALIGN_CENTER);
215 attr.SetOrientation(wxHORIZONTAL);
216 attr.SetReadOnly(true);
217 attr.SetForegroundColour(GetPlayerColor(m_doc, p_coords.GetCol()));
218 return attr;
219 }
220 else if (IsCornerLabelCell(p_coords)) {
221 return GetSheetRefData()->m_defaultCornerLabelAttr;
222 }
223
224 wxSheetCellAttr attr(GetSheetRefData()->m_defaultGridCellAttr);
225 attr.SetFont(wxFont(10, wxSWISS, wxNORMAL, wxNORMAL));
226 attr.SetAlignment(wxALIGN_RIGHT, wxALIGN_CENTER);
227 attr.SetOrientation(wxHORIZONTAL);
228 attr.SetForegroundColour(GetPlayerColor(m_doc, p_coords.GetCol()));
229 attr.SetReadOnly(true);
230 return attr;
231 }
232
233 class LogitBranchDialog : public wxDialog {
234 private:
235 LogitMixedSheet *m_sheet;
236
237 public:
238 LogitBranchDialog(wxWindow *p_parent,
239 gbtGameDocument *p_doc,
240 LogitMixedBranch &p_branch);
241 };
242
LogitBranchDialog(wxWindow * p_parent,gbtGameDocument * p_doc,LogitMixedBranch & p_branch)243 LogitBranchDialog::LogitBranchDialog(wxWindow *p_parent,
244 gbtGameDocument *p_doc,
245 LogitMixedBranch &p_branch)
246 : wxDialog(p_parent, wxID_ANY, wxT("Logit equilibrium correspondence"),
247 wxDefaultPosition)
248 {
249 m_sheet = new LogitMixedSheet(this, p_doc, p_branch);
250 m_sheet->AutoSizeCol(0);
251
252 wxBoxSizer *sizer = new wxBoxSizer(wxVERTICAL);
253 sizer->Add(m_sheet, 0, wxALL, 5);
254 sizer->Add(CreateButtonSizer(wxOK), 0, wxALL | wxEXPAND, 5);
255
256 SetSizer(sizer);
257 sizer->Fit(this);
258 sizer->SetSizeHints(this);
259 Layout();
260 CenterOnParent();
261 }
262
263
264 //========================================================================
265 // class gbtLogitPlotCtrl
266 //========================================================================
267
268 class gbtLogitPlotCtrl : public wxPlotCtrl {
269 private:
270 // gbtGameDocument *m_doc;
271 double m_scaleFactor;
272
273 /// Overriding x (lambda) axis labeling
274 void CalcXAxisTickPositions(void);
275
276 public:
277 gbtLogitPlotCtrl(wxWindow *p_parent, gbtGameDocument *p_doc);
278
LambdaToX(double p_lambda)279 double LambdaToX(double p_lambda)
280 { return m_scaleFactor*p_lambda / (1.0 + m_scaleFactor*p_lambda); }
XToLambda(double p_x)281 double XToLambda(double p_x)
282 { return p_x / (m_scaleFactor * (1.0 - p_x)); }
283
SetScaleFactor(double p_scale)284 void SetScaleFactor(double p_scale) { m_scaleFactor = p_scale; }
285 };
286
gbtLogitPlotCtrl(wxWindow * p_parent,gbtGameDocument * p_doc)287 gbtLogitPlotCtrl::gbtLogitPlotCtrl(wxWindow *p_parent,
288 gbtGameDocument *p_doc)
289 : wxPlotCtrl(p_parent), /* m_doc(p_doc), */ m_scaleFactor(1.0)
290 {
291 SetAxisLabelColour(*wxBLUE);
292 wxFont labelFont(8, wxSWISS, wxNORMAL, wxBOLD);
293 SetAxisLabelFont(labelFont);
294 SetAxisColour(*wxBLUE);
295 SetAxisFont(labelFont);
296 SetDrawSymbols(false);
297
298 // SetAxisFont resets the width of the y axis labels, assuming
299 // a fairly long label.
300 int x=6, y=12, descent=0, leading=0;
301 GetTextExtent(wxT("0.88"), &x, &y, &descent, &leading, &labelFont);
302 m_y_axis_text_width = x + leading;
303
304 SetXAxisLabel(wxT("Lambda"));
305 SetShowXAxisLabel(true);
306 SetYAxisLabel(wxT("Probability"));
307 SetShowYAxisLabel(true);
308
309 SetShowKey(true);
310
311 m_xAxisTick_step = 0.2;
312 SetViewRect(wxRect2DDouble(0, 0, 1, 1));
313 }
314
315 //
316 // This differs from the wxPlotWindow original only by the use of
317 // XToLambda() to construct the tick labels.
318 //
CalcXAxisTickPositions(void)319 void gbtLogitPlotCtrl::CalcXAxisTickPositions(void)
320 {
321 double current = ceil(m_viewRect.GetLeft() / m_xAxisTick_step) * m_xAxisTick_step;
322 m_xAxisTicks.Clear();
323 m_xAxisTickLabels.Clear();
324 int i, x, windowWidth = GetPlotAreaRect().width;
325 for (i=0; i<m_xAxisTick_count; i++) {
326 if (!IsFinite(current, wxT("axis label is not finite"))) return;
327
328 x = GetClientCoordFromPlotX( current );
329
330 if ((x >= -1) && (x < windowWidth+2)) {
331 m_xAxisTicks.Add(x);
332 m_xAxisTickLabels.Add(wxString::Format(m_xAxisTickFormat.c_str(),
333 XToLambda(current)));
334 }
335
336 current += m_xAxisTick_step;
337 }
338 }
339
340
341 //========================================================================
342 // class gbtLogitPlotStrategyList
343 //========================================================================
344
345 class gbtLogitPlotStrategyList : public wxSheet {
346 private:
347 gbtGameDocument *m_doc;
348
349 //!
350 //! @name Overriding wxSheet members to disable selection behavior
351 //!
352 //@{
SelectRow(int,bool=false,bool=false)353 bool SelectRow(int, bool = false, bool = false)
354 { return false; }
SelectRows(int,int,bool=false,bool=false)355 bool SelectRows(int, int, bool = false, bool = false)
356 { return false; }
SelectCol(int,bool=false,bool=false)357 bool SelectCol(int, bool = false, bool = false)
358 { return false; }
SelectCols(int,int,bool=false,bool=false)359 bool SelectCols(int, int, bool = false, bool = false)
360 { return false; }
SelectCell(const wxSheetCoords &,bool=false,bool=false)361 bool SelectCell(const wxSheetCoords&, bool = false, bool = false)
362 { return false; }
SelectBlock(const wxSheetBlock &,bool=false,bool=false)363 bool SelectBlock(const wxSheetBlock&, bool = false, bool = false)
364 { return false; }
SelectAll(bool=false)365 bool SelectAll(bool = false) { return false; }
366
HasSelection(bool=true) const367 bool HasSelection(bool = true) const { return false; }
IsCellSelected(const wxSheetCoords &) const368 bool IsCellSelected(const wxSheetCoords &) const { return false; }
IsRowSelected(int) const369 bool IsRowSelected(int) const { return false; }
IsColSelected(int) const370 bool IsColSelected(int) const { return false; }
DeselectBlock(const wxSheetBlock &,bool=false)371 bool DeselectBlock(const wxSheetBlock &, bool = false) { return false; }
ClearSelection(bool=false)372 bool ClearSelection(bool = false) { return false; }
373 //@}
374
375 /// Overriding wxSheet member to suppress drawing of cursor
DrawCursorCellHighlight(wxDC &,const wxSheetCellAttr &)376 void DrawCursorCellHighlight(wxDC&, const wxSheetCellAttr &) { }
377
378 // Event handlers
379 // This disables moving the (unseen) cursor
OnLeftDown(wxSheetEvent &)380 void OnLeftDown(wxSheetEvent &) { }
381 void OnLeftUp(wxSheetEvent &);
382
383 public:
384 gbtLogitPlotStrategyList(wxWindow *p_parent, gbtGameDocument *p_doc);
385
IsStrategyShown(int p_index)386 bool IsStrategyShown(int p_index)
387 { return GetCellValue(wxSheetCoords(p_index-1, 2)) == wxT("1"); }
388 };
389
gbtLogitPlotStrategyList(wxWindow * p_parent,gbtGameDocument * p_doc)390 gbtLogitPlotStrategyList::gbtLogitPlotStrategyList(wxWindow *p_parent,
391 gbtGameDocument *p_doc)
392 : wxSheet(p_parent, wxID_ANY), m_doc(p_doc)
393 {
394 CreateGrid(m_doc->GetGame()->MixedProfileLength(), 3);
395
396 SetRowLabelWidth(0);
397 SetColLabelHeight(0);
398 SetGridLineColour(*wxWHITE);
399
400 for (int st = 1; st <= m_doc->GetGame()->MixedProfileLength(); st++) {
401 GameStrategy strategy = m_doc->GetGame()->GetStrategy(st);
402 GamePlayer player = strategy->GetPlayer();
403 wxColour color = m_doc->GetStyle().GetPlayerColor(player->GetNumber());
404
405 if (strategy->GetNumber() == 1) {
406 SetCellSpan(wxSheetCoords(st-1, 0),
407 wxSheetCoords(player->NumStrategies(), 1));
408 SetCellValue(wxSheetCoords(st-1, 0),
409 wxString(player->GetLabel().c_str(), *wxConvCurrent));
410 SetAttrForegroundColour(wxSheetCoords(st-1, 0), color);
411 SetAttrAlignment(wxSheetCoords(st-1, 0),
412 wxALIGN_CENTER | wxALIGN_CENTER_VERTICAL);
413 }
414
415
416 SetCellValue(wxSheetCoords(st-1, 1),
417 wxString(strategy->GetLabel().c_str(), *wxConvCurrent));
418 SetAttrForegroundColour(wxSheetCoords(st-1, 1), color);
419
420 SetCellValue(wxSheetCoords(st-1, 2), wxT("1"));
421 SetAttrForegroundColour(wxSheetCoords(st-1, 2), color);
422 SetAttrRenderer(wxSheetCoords(st-1, 2),
423 wxSheetCellRenderer(new wxSheetCellBoolRendererRefData()));
424 SetAttrEditor(wxSheetCoords(st-1, 2),
425 wxSheetCellEditor(new wxSheetCellBoolEditorRefData()));
426 }
427
428 AutoSizeCols();
429
430 Connect(GetId(), wxEVT_SHEET_CELL_LEFT_DOWN,
431 (wxObjectEventFunction) (wxEventFunction) wxStaticCastEvent(wxSheetEventFunction, wxSheetEventFunction(&gbtLogitPlotStrategyList::OnLeftDown)));
432
433 Connect(GetId(), wxEVT_SHEET_CELL_LEFT_UP,
434 (wxObjectEventFunction) (wxEventFunction) wxStaticCastEvent(wxSheetEventFunction, wxSheetEventFunction(&gbtLogitPlotStrategyList::OnLeftUp)));
435 }
436
OnLeftUp(wxSheetEvent & p_event)437 void gbtLogitPlotStrategyList::OnLeftUp(wxSheetEvent &p_event)
438 {
439 if (p_event.GetCoords().GetCol() != 2) {
440 return;
441 }
442
443 if (GetCellValue(p_event.GetCoords()) == wxT("1")) {
444 SetCellValue(p_event.GetCoords(), wxT("0"));
445 }
446 else {
447 SetCellValue(p_event.GetCoords(), wxT("1"));
448 }
449
450 // Allow normal processing -- parent window will want to update
451 p_event.Skip();
452 }
453
454
455 //========================================================================
456 // class LogitPlotPanel
457 //========================================================================
458
459 class LogitPlotPanel : public wxPanel {
460 private:
461 gbtGameDocument *m_doc;
462 LogitMixedBranch m_branch;
463 gbtLogitPlotStrategyList *m_plotStrategies;
464 gbtLogitPlotCtrl *m_plotCtrl;
465
466 // Event handlers
OnChangeStrategies(wxSheetEvent &)467 void OnChangeStrategies(wxSheetEvent &) { Plot(); }
468
469 public:
470 LogitPlotPanel(wxWindow *p_parent, gbtGameDocument *p_doc);
471
AddProfile(const wxString & p_text)472 void AddProfile(const wxString &p_text)
473 { m_branch.AddProfile(p_text); }
474
475 void SetScaleFactor(double p_scale);
476 void FitZoom(void);
477 void Plot(void);
478
GetBranch(void)479 LogitMixedBranch &GetBranch(void) { return m_branch; }
GetPlotCtrl(void) const480 wxPlotCtrl *GetPlotCtrl(void) const { return m_plotCtrl; }
481 };
482
LogitPlotPanel(wxWindow * p_parent,gbtGameDocument * p_doc)483 LogitPlotPanel::LogitPlotPanel(wxWindow *p_parent,
484 gbtGameDocument *p_doc)
485 : wxPanel(p_parent, wxID_ANY), m_doc(p_doc), m_branch(p_doc)
486 {
487 m_plotCtrl = new gbtLogitPlotCtrl(this, p_doc);
488 m_plotCtrl->SetSizeHints(wxSize(600, 400));
489
490 m_plotStrategies = new gbtLogitPlotStrategyList(this, p_doc);
491 wxStaticBoxSizer *playerSizer =
492 new wxStaticBoxSizer(wxHORIZONTAL, this, wxT("Show strategies"));
493 playerSizer->Add(m_plotStrategies, 1, wxALL | wxEXPAND, 5);
494
495 wxBoxSizer *sizer = new wxBoxSizer(wxHORIZONTAL);
496 sizer->Add(playerSizer, 0, wxALL | wxEXPAND, 5);
497 sizer->Add(m_plotCtrl, 0, wxALL, 5);
498
499 Connect(m_plotStrategies->GetId(), wxEVT_SHEET_CELL_LEFT_UP,
500 (wxObjectEventFunction) (wxEventFunction) wxStaticCastEvent(wxSheetEventFunction, wxSheetEventFunction(&LogitPlotPanel::OnChangeStrategies)));
501
502 SetSizer(sizer);
503 Layout();
504 }
505
Plot(void)506 void LogitPlotPanel::Plot(void)
507 {
508 if (m_branch.NumPoints() == 0) return;
509
510 m_plotCtrl->DeleteCurve(-1);
511
512 for (int st = 1; st <= m_doc->GetGame()->MixedProfileLength(); st++) {
513 if (!m_plotStrategies->IsStrategyShown(st)) continue;
514
515 wxPlotData *curve = new wxPlotData(m_branch.NumPoints());
516
517 GameStrategy strategy = m_doc->GetGame()->GetStrategy(st);
518 GamePlayer player = strategy->GetPlayer();
519
520 curve->SetFilename(wxString(player->GetLabel().c_str(), *wxConvCurrent) +
521 wxT(":") +
522 wxString(strategy->GetLabel().c_str(), *wxConvCurrent));
523
524 for (int i = 0; i < m_branch.NumPoints(); i++) {
525 curve->SetValue(i, m_plotCtrl->LambdaToX(m_branch.GetLambda(i+1)),
526 m_branch.GetProfile(i+1)[st]);
527 }
528
529 curve->SetPen(wxPLOTPEN_NORMAL,
530 wxPen(m_doc->GetStyle().GetPlayerColor(player->GetNumber()),
531 1, wxSOLID));
532
533 m_plotCtrl->AddCurve(curve, false);
534 }
535 }
536
SetScaleFactor(double p_scale)537 void LogitPlotPanel::SetScaleFactor(double p_scale)
538 {
539 m_plotCtrl->SetScaleFactor(p_scale);
540 Plot();
541 }
542
FitZoom(void)543 void LogitPlotPanel::FitZoom(void)
544 {
545 m_plotCtrl->MakeCurveVisible(-1);
546 }
547
548 //========================================================================
549 // class LogitPrintout
550 //========================================================================
551
552 class LogitPrintout : public wxPrintout {
553 private:
554 wxPlotCtrl *m_plot;
555
556 public:
LogitPrintout(wxPlotCtrl * p_plot,const wxString & p_label)557 LogitPrintout(wxPlotCtrl *p_plot, const wxString &p_label)
558 : wxPrintout(p_label), m_plot(p_plot) { }
~LogitPrintout()559 virtual ~LogitPrintout() { }
560
OnPrintPage(int)561 bool OnPrintPage(int)
562 { wxSize size = GetDC()->GetSize();
563 m_plot->DrawWholePlot(GetDC(), wxRect(50, 50,
564 size.GetWidth() - 100,
565 size.GetHeight() - 100));
566 return true;
567 }
HasPage(int page)568 bool HasPage(int page) { return (page <= 1); }
GetPageInfo(int * minPage,int * maxPage,int * selPageFrom,int * selPageTo)569 void GetPageInfo(int *minPage, int *maxPage,
570 int *selPageFrom, int *selPageTo)
571 { *minPage = 1; *maxPage = 1; *selPageFrom = 1; *selPageTo = 1; }
572 };
573
574
575 //========================================================================
576 // class LogitMixedDialog
577 //========================================================================
578
579 class LogitMixedDialog : public wxDialog {
580 private:
581 gbtGameDocument *m_doc;
582 int m_pid;
583 wxProcess *m_process;
584 LogitPlotPanel *m_plot;
585 wxSpinCtrlDbl *m_scaler;
586 wxToolBar *m_toolBar;
587 wxStaticText *m_statusText;
588 wxButton *m_stopButton, *m_okButton;
589 wxTimer m_timer;
590 wxString m_output;
591
592 void OnStop(wxCommandEvent &);
593 void OnTimer(wxTimerEvent &);
594 void OnIdle(wxIdleEvent &);
595 void OnEndProcess(wxProcessEvent &);
596 void OnSave(wxCommandEvent &);
597 void OnPrint(wxCommandEvent &);
598
599 void OnChangeScale(wxSpinEvent &);
600 void OnZoomFit(wxCommandEvent &);
601 void OnViewData(wxCommandEvent &);
602
603 void Start(void);
604
605 public:
606 LogitMixedDialog(wxWindow *p_parent, gbtGameDocument *p_doc);
607
608 DECLARE_EVENT_TABLE()
609 };
610
611 const int GBT_ID_TIMER = 2000;
612 const int GBT_ID_PROCESS = 2001;
613 const int GBT_MENU_VIEW_DATA = 2002;
614
BEGIN_EVENT_TABLE(LogitMixedDialog,wxDialog)615 BEGIN_EVENT_TABLE(LogitMixedDialog, wxDialog)
616 EVT_END_PROCESS(GBT_ID_PROCESS, LogitMixedDialog::OnEndProcess)
617 EVT_IDLE(LogitMixedDialog::OnIdle)
618 EVT_TIMER(GBT_ID_TIMER, LogitMixedDialog::OnTimer)
619 EVT_MENU(wxID_SAVE, LogitMixedDialog::OnSave)
620 EVT_MENU(wxID_PRINT, LogitMixedDialog::OnPrint)
621 EVT_MENU(GBT_MENU_VIEW_ZOOMFIT, LogitMixedDialog::OnZoomFit)
622 EVT_MENU(GBT_MENU_VIEW_DATA, LogitMixedDialog::OnViewData)
623 END_EVENT_TABLE()
624
625 #include "bitmaps/stop.xpm"
626 #include "bitmaps/print.xpm"
627 #include "bitmaps/savedata.xpm"
628 #include "bitmaps/datasrc.xpm"
629 #include "bitmaps/zoomfit.xpm"
630
631 LogitMixedDialog::LogitMixedDialog(wxWindow *p_parent,
632 gbtGameDocument *p_doc)
633 : wxDialog(p_parent, -1, wxT("Compute quantal response equilibria"),
634 wxDefaultPosition),
635 m_doc(p_doc), m_process(0), m_timer(this, GBT_ID_TIMER)
636 {
637 wxBoxSizer *sizer = new wxBoxSizer(wxVERTICAL);
638
639 wxBoxSizer *startSizer = new wxBoxSizer(wxHORIZONTAL);
640
641 m_statusText = new wxStaticText(this, wxID_STATIC,
642 wxT("The computation is currently in progress."));
643 m_statusText->SetForegroundColour(*wxBLUE);
644 startSizer->Add(m_statusText, 0, wxALL | wxALIGN_CENTER, 5);
645
646 m_stopButton = new wxBitmapButton(this, wxID_CANCEL, wxBitmap(stop_xpm));
647 m_stopButton->SetToolTip(_("Stop the computation"));
648 startSizer->Add(m_stopButton, 0, wxALL | wxALIGN_CENTER, 5);
649 Connect(wxID_CANCEL, wxEVT_COMMAND_BUTTON_CLICKED,
650 wxCommandEventHandler(LogitMixedDialog::OnStop));
651
652 sizer->Add(startSizer, 0, wxALL | wxALIGN_CENTER, 5);
653
654 m_toolBar = new wxToolBar(this, wxID_ANY);
655 m_toolBar->SetWindowStyle(wxTB_HORIZONTAL | wxTB_FLAT);
656 m_toolBar->SetMargins(4, 4);
657 m_toolBar->SetToolBitmapSize(wxSize(24, 24));
658
659 m_toolBar->AddTool(wxID_SAVE,
660 wxEmptyString,
661 wxBitmap(savedata_xpm),
662 wxNullBitmap,
663 wxITEM_NORMAL,
664 _("Save the correspondence to a CSV file"),
665 _("Save the correspondence to a CSV file"));
666
667 m_toolBar->EnableTool(wxID_SAVE, false);
668
669 m_toolBar->AddTool(GBT_MENU_VIEW_DATA,
670 wxEmptyString,
671 wxBitmap(datasrc_xpm),
672 wxNullBitmap,
673 wxITEM_NORMAL,
674 _("View the points in the correspondence"),
675 _("View the points in the correspondence"));
676
677 m_toolBar->EnableTool(GBT_MENU_VIEW_DATA, false);
678
679 m_toolBar->AddTool(wxID_PRINT,
680 wxEmptyString,
681 wxBitmap(print_xpm),
682 wxNullBitmap,
683 wxITEM_NORMAL,
684 _("Print the graph"),
685 _("Print the graph"));
686
687 m_toolBar->AddSeparator();
688
689 m_toolBar->AddTool(GBT_MENU_VIEW_ZOOMFIT,
690 wxEmptyString,
691 wxBitmap(zoomfit_xpm),
692 wxNullBitmap,
693 wxITEM_NORMAL,
694 _("Show the whole graph"),
695 _("Show the whole graph"));
696
697 m_toolBar->AddControl(new wxStaticText(m_toolBar, wxID_STATIC,
698 wxT("Graph scaling:")));
699 m_scaler = new wxSpinCtrlDbl(*m_toolBar, wxID_ANY,
700 wxT(""),
701 wxDefaultPosition, wxDefaultSize,
702 0, 0.1, 10.0, 1.0, 0.1);
703 m_toolBar->AddControl(m_scaler);
704 Connect(m_scaler->GetId(), wxEVT_COMMAND_SPINCTRL_UPDATED,
705 wxSpinEventHandler(LogitMixedDialog::OnChangeScale));
706
707 m_toolBar->Realize();
708 sizer->Add(m_toolBar, 0, wxALL | wxEXPAND, 5);
709
710
711 wxBoxSizer *midSizer = new wxBoxSizer(wxHORIZONTAL);
712 m_plot = new LogitPlotPanel(this, m_doc);
713 midSizer->Add(m_plot, 0, wxALL | wxALIGN_CENTER, 5);
714
715 sizer->Add(midSizer, 0, wxALL | wxALIGN_CENTER, 5);
716
717 wxBoxSizer *buttonSizer = new wxBoxSizer(wxHORIZONTAL);
718 m_okButton = new wxButton(this, wxID_OK, wxT("OK"));
719 buttonSizer->Add(m_okButton, 0, wxALL | wxALIGN_CENTER, 5);
720 m_okButton->Enable(false);
721
722 sizer->Add(buttonSizer, 0, wxALL | wxALIGN_RIGHT, 5);
723
724 SetSizer(sizer);
725 sizer->Fit(this);
726 sizer->SetSizeHints(this);
727 Layout();
728 CenterOnParent();
729 Start();
730 }
731
Start(void)732 void LogitMixedDialog::Start(void)
733 {
734 m_doc->BuildNfg();
735
736 m_process = new wxProcess(this, GBT_ID_PROCESS);
737 m_process->Redirect();
738
739 #ifdef __WXMAC__
740 m_pid = wxExecute(wxStandardPaths::Get().GetExecutablePath() +
741 wxT("-logit -S"),
742 wxEXEC_ASYNC, m_process);
743 #else
744 m_pid = wxExecute(wxT("gambit-logit -S"), wxEXEC_ASYNC, m_process);
745 #endif // __WXMAC__
746
747 std::ostringstream s;
748 m_doc->GetGame()->Write(s, "nfg");
749 wxString str(wxString(s.str().c_str(), *wxConvCurrent));
750
751 // It is possible that the whole string won't write on one go, so
752 // we should take this possibility into account. If the write doesn't
753 // complete the whole way, we take a 100-millisecond siesta and try
754 // again. (This seems to primarily be an issue with -- you guessed it --
755 // Windows!)
756 while (str.length() > 0) {
757 wxTextOutputStream os(*m_process->GetOutputStream());
758
759 // It appears that (at least with mingw) the string itself contains
760 // only '\n' for newlines. If we don't SetMode here, these get
761 // converted to '\r\n' sequences, and so the number of characters
762 // LastWrite() returns does not match the number of characters in
763 // our string. Setting this explicitly solves this problem.
764 os.SetMode(wxEOL_UNIX);
765 os.WriteString(str);
766 str.Remove(0, m_process->GetOutputStream()->LastWrite());
767 wxMilliSleep(100);
768 }
769 m_process->CloseOutput();
770
771 m_timer.Start(1000, false);
772 }
773
OnIdle(wxIdleEvent & p_event)774 void LogitMixedDialog::OnIdle(wxIdleEvent &p_event)
775 {
776 if (!m_process) return;
777
778 if (m_process->IsInputAvailable()) {
779 wxTextInputStream tis(*m_process->GetInputStream());
780
781 wxString msg;
782 msg << tis.ReadLine();
783 m_plot->AddProfile(msg);
784 //m_mixedList->AddProfile(msg, false);
785 m_output += msg;
786 m_output += wxT("\n");
787
788 p_event.RequestMore();
789 }
790 else {
791 m_timer.Start(1000, false);
792 }
793 }
794
OnTimer(wxTimerEvent & p_event)795 void LogitMixedDialog::OnTimer(wxTimerEvent &p_event)
796 {
797 wxWakeUpIdle();
798 }
799
OnEndProcess(wxProcessEvent & p_event)800 void LogitMixedDialog::OnEndProcess(wxProcessEvent &p_event)
801 {
802 m_stopButton->Enable(false);
803 m_timer.Stop();
804
805 while (m_process->IsInputAvailable()) {
806 wxTextInputStream tis(*m_process->GetInputStream());
807
808 wxString msg;
809 msg << tis.ReadLine();
810
811 if (msg != wxT("")) {
812 m_plot->AddProfile(msg);
813 //m_mixedList->AddProfile(msg, true);
814 m_output += msg;
815 m_output += wxT("\n");
816 }
817 }
818
819 if (p_event.GetExitCode() == 0) {
820 m_statusText->SetLabel(wxT("The computation has completed."));
821 m_statusText->SetForegroundColour(wxColour(0, 192, 0));
822 }
823 else {
824 m_statusText->SetLabel(wxT("The computation ended abnormally."));
825 m_statusText->SetForegroundColour(*wxRED);
826 }
827
828 m_okButton->Enable(true);
829 m_toolBar->EnableTool(wxID_SAVE, true);
830 m_toolBar->EnableTool(GBT_MENU_VIEW_DATA, true);
831 m_plot->Plot();
832 }
833
OnStop(wxCommandEvent &)834 void LogitMixedDialog::OnStop(wxCommandEvent &)
835 {
836 // Per the wxWidgets wiki, under Windows, programs that run
837 // without a console window don't respond to the more polite
838 // SIGTERM, so instead we must be rude and SIGKILL it.
839 m_stopButton->Enable(false);
840
841 #ifdef __WXMSW__
842 wxProcess::Kill(m_pid, wxSIGKILL);
843 #else
844 wxProcess::Kill(m_pid, wxSIGTERM);
845 #endif // __WXMSW__
846 }
847
OnSave(wxCommandEvent &)848 void LogitMixedDialog::OnSave(wxCommandEvent &)
849 {
850 wxFileDialog dialog(this, _("Choose file"), wxT(""), wxT(""),
851 wxT("CSV files (*.csv)|*.csv|")
852 wxT("All files (*.*)|*.*"),
853 wxFD_SAVE | wxFD_OVERWRITE_PROMPT);
854
855 if (dialog.ShowModal() == wxID_OK) {
856 std::ofstream file((const char *) dialog.GetPath().mb_str());
857 file << ((const char *) m_output.mb_str());
858 }
859 }
860
OnPrint(wxCommandEvent &)861 void LogitMixedDialog::OnPrint(wxCommandEvent &)
862 {
863 wxPrintDialogData data;
864 wxPrinter printer(&data);
865
866 wxPrintout *printout = new LogitPrintout(m_plot->GetPlotCtrl(),
867 wxT("Logit correspondence"));
868
869 if (!printer.Print(this, printout, true)) {
870 if (wxPrinter::GetLastError() == wxPRINTER_ERROR) {
871 wxMessageBox(_("There was an error in printing"), _("Error"), wxOK);
872 }
873 // Otherwise, user hit "cancel"; just be quiet and return.
874 return;
875 }
876 }
877
OnChangeScale(wxSpinEvent &)878 void LogitMixedDialog::OnChangeScale(wxSpinEvent &)
879 {
880 m_plot->SetScaleFactor(m_scaler->GetValue());
881 }
882
OnZoomFit(wxCommandEvent &)883 void LogitMixedDialog::OnZoomFit(wxCommandEvent &)
884 {
885 m_plot->FitZoom();
886 }
887
OnViewData(wxCommandEvent &)888 void LogitMixedDialog::OnViewData(wxCommandEvent &)
889 {
890 LogitBranchDialog dialog(this, m_doc, m_plot->GetBranch());
891 dialog.ShowModal();
892 }
893
894
895 } // end encapsulating anonymous namespace
896
897 //========================================================================
898 // External interface
899 //========================================================================
900
LogitStrategic(wxWindow * p_parent,gbtGameDocument * p_doc)901 void LogitStrategic(wxWindow *p_parent, gbtGameDocument *p_doc)
902 {
903 LogitMixedDialog(p_parent, p_doc).ShowModal();
904 }
905