Logo Search packages:      
Sourcecode: gambit version File versions  Download package

nfgtable.cc

//
// $Source: /cvsroot/gambit/gambit/sources/gui/nfgtable.cc,v $
// $Date: 2006/01/16 18:49:38 $
// $Revision: 1.68 $
//
// DESCRIPTION:
// Implementation of strategic game matrix display/editor
//
// 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 <wx/wxprec.h>
#ifndef WX_PRECOMP
#include <wx/wx.h>
#endif  // WX_PRECOMP
#include <wx/dnd.h>      // for drag-and-drop support
#include <wx/print.h>    // for printing support
#include "dcsvg.h"         // for SVG output

#include "wx/sheet/sheet.h"

#include "renratio.h"    // special renderer for rational numbers

#include "gamedoc.h"
#include "nfgpanel.h"
#include "nfgtable.h"

//=========================================================================
//                       class gbtTableWidgetBase
//=========================================================================

//!
//! This class handles some common overriding of wxSheet behavior
//! common to the sheets used in the strategic game display.
//!
00051 class gbtTableWidgetBase : public wxSheet {
protected:
  //!
  //! @name 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; }

  bool HasSelection(bool = true) const { return false; }
  bool IsCellSelected(const wxSheetCoords &) const { return false; }
  bool IsRowSelected(int) const { return false; }
  bool IsColSelected(int) const { return false; }
  bool DeselectBlock(const wxSheetBlock &, bool = false) { return false; }
  bool ClearSelection(bool = false) { return false; }
  //@}

  /// Overriding wxSheet member to suppress drawing of cursor
00080   void DrawCursorCellHighlight(wxDC&, const wxSheetCellAttr &) { }

  /// Overriding wxSheet member to show editor on one click
  void OnCellLeftClick(wxSheetEvent &);

public:
  /// @name Lifecycle
  //@{
  /// Constructor
00089   gbtTableWidgetBase(wxWindow *p_parent, wxWindowID p_id,
                 const wxPoint &p_pos = wxDefaultPosition,
                 const wxSize &p_size = wxDefaultSize,
                 long p_style = wxWANTS_CHARS,
                 const wxString &p_name = wxT("wxSheet"))
    : wxSheet(p_parent, p_id, p_pos, p_size, p_style, p_name)
  {
    Connect(GetId(), wxEVT_SHEET_CELL_LEFT_DOWN,
          (wxObjectEventFunction) (wxEventFunction) wxStaticCastEvent(wxSheetEventFunction, wxSheetEventFunction(&gbtTableWidgetBase::OnCellLeftClick)));
  }
  //@}

  /// @name Drop target interaction
  //@{
  /// Called when the drop target receives text.
00104   virtual bool DropText(int p_x, int p_y, const wxString &p_text)
  { return false; }
  //@}

  /// @name Access to scrollbars from underlying wxSheet
  //@{
  /// Get the vertical scrollbar
00111   wxScrollBar *GetVerticalScrollBar(void) const
  { return m_vertScrollBar; }
  /// Get the horizontal scrollbar
00114   wxScrollBar *GetHorizontalScrollBar(void) const 
  { return m_horizScrollBar; }
  //@}
};

00119 void gbtTableWidgetBase::OnCellLeftClick(wxSheetEvent &p_event)
{
  SetGridCursorCell(p_event.GetCoords());
  EnableCellEditControl(p_event.GetCoords());
}

//=========================================================================
//                class gbtTableWidgetDropTarget
//=========================================================================

//!
//! This simple class serves as a drop target for players; it simply
//! communicates the location and text of the drop to its owner for
//! further processing
//!
00134 class gbtTableWidgetDropTarget : public wxTextDropTarget {
private:
  gbtTableWidgetBase *m_owner;

public:
  gbtTableWidgetDropTarget(gbtTableWidgetBase *p_owner) : m_owner(p_owner) { }

  bool OnDropText(wxCoord x, wxCoord y, const wxString &p_text)
  { return m_owner->DropText(x, y, p_text); }
};


//=========================================================================
//                       class gbtRowPlayerWidget 
//=========================================================================

class gbtRowPlayerWidget : public gbtTableWidgetBase {
private:
  gbtGameDocument *m_doc;
  gbtTableWidget *m_table;

  /// @name Overriding wxSheet members for data access
  //@{
  /// Returns the value in the cell
  wxString GetCellValue(const wxSheetCoords &);
  /// Sets the value in the cell, by relabeling the strategy
  void SetCellValue(const wxSheetCoords &, const wxString &);
  /// Returns the attributes of the cell
  wxSheetCellAttr GetAttr(const wxSheetCoords &p_coords, 
                    wxSheetAttr_Type) const;
  //@}

  /// @name Overriding wxSheet members to customize drawing
  //@{
  /// Overrides to draw dominance indicators
  void DrawCell(wxDC &p_dc, const wxSheetCoords &p_coords);
  //@}  

  void OnCellRightClick(wxSheetEvent &);

public:
  /// @name Lifecycle
  //@{
  /// Constructor
  gbtRowPlayerWidget(gbtTableWidget *p_parent, gbtGameDocument *p_doc);
  //@}

  /// @name Synchronizing with document state
  //@{
  void OnUpdate(void);
  //@}

  /// @name Drop target interaction
  //@{
  /// Called when the drop target receives text. 
  bool DropText(int p_x, int p_y, const wxString &p_text);
  //@}
};

gbtRowPlayerWidget::gbtRowPlayerWidget(gbtTableWidget *p_parent,
                               gbtGameDocument *p_doc)
  : gbtTableWidgetBase(p_parent, -1), m_doc(p_doc), m_table(p_parent)
{
  CreateGrid(m_table->NumRowContingencies(), m_table->NumRowPlayers());
  SetRowLabelWidth(1);
  SetColLabelHeight(1);
  SetScrollBarMode(SB_NEVER);
  SetGridLineColour(*wxBLACK);

  SetDropTarget(new gbtTableWidgetDropTarget(this));

  Connect(GetId(), wxEVT_SHEET_CELL_RIGHT_DOWN,
        (wxObjectEventFunction) (wxEventFunction) wxStaticCastEvent(wxSheetEventFunction, wxSheetEventFunction(&gbtRowPlayerWidget::OnCellRightClick)));

}

void gbtRowPlayerWidget::OnCellRightClick(wxSheetEvent &p_event)
{
  if (m_table->NumRowPlayers() == 0) {
    p_event.Skip();
    return;
  }

  const Gambit::StrategySupport &support = m_doc->GetNfgSupport();
  wxSheetCoords coords = p_event.GetCoords();

  int player = m_table->GetRowPlayer(coords.GetCol() + 1);
  int strat = m_table->RowToStrategy(coords.GetCol() + 1, coords.GetRow());

  support.GetStrategy(player, strat)->DeleteStrategy();
  m_doc->UpdateViews(GBT_DOC_MODIFIED_GAME);
}

wxString gbtRowPlayerWidget::GetCellValue(const wxSheetCoords &p_coords)
{
  if (IsLabelCell(p_coords)) return wxT("");

  if (m_table->NumRowPlayers() == 0) return wxT("Payoffs");

  const Gambit::StrategySupport &support = m_doc->GetNfgSupport();

  int player = m_table->GetRowPlayer(p_coords.GetCol() + 1);
  int strat = m_table->RowToStrategy(p_coords.GetCol() + 1, p_coords.GetRow());

  return wxString(support.GetStrategy(player, strat)->GetLabel().c_str(), 
              *wxConvCurrent);
}

void gbtRowPlayerWidget::SetCellValue(const wxSheetCoords &p_coords,
                              const wxString &p_value)
{
  const Gambit::StrategySupport &support = m_doc->GetNfgSupport();

  int player = m_table->GetRowPlayer(p_coords.GetCol() + 1);
  int strat = m_table->RowToStrategy(p_coords.GetCol() + 1, p_coords.GetRow());

  support.GetStrategy(player, strat)->SetLabel((const char *) p_value.mb_str());
  m_doc->UpdateViews(GBT_DOC_MODIFIED_LABELS);
}

wxSheetCellAttr gbtRowPlayerWidget::GetAttr(const wxSheetCoords &p_coords,
                                  wxSheetAttr_Type) const 
{
  wxSheetCellAttr attr(GetSheetRefData()->m_defaultGridCellAttr);
  attr.SetFont(wxFont(10, wxSWISS, wxNORMAL, wxBOLD));
  attr.SetAlignment(wxALIGN_CENTER, wxALIGN_CENTER);
  attr.SetOrientation(wxHORIZONTAL);
  if (m_table->NumRowPlayers() > 0) {
    attr.SetForegroundColour(m_doc->GetStyle().GetPlayerColor(m_table->GetRowPlayer(p_coords.GetCol()+1)));
    attr.SetReadOnly(m_doc->IsTree());
  }
  else {
    attr.SetForegroundColour(*wxBLACK);
    attr.SetReadOnly(true);
  }
  attr.SetBackgroundColour(*wxLIGHT_GREY);
  return attr;
}

void gbtRowPlayerWidget::DrawCell(wxDC &p_dc, const wxSheetCoords &p_coords)
{
  gbtTableWidgetBase::DrawCell(p_dc, p_coords);

  if (!m_table->ShowDominance() || IsLabelCell(p_coords) ||
      m_table->NumRowPlayers() == 0)  return;

  const Gambit::StrategySupport &support = m_doc->GetNfgSupport();
  int player = m_table->GetRowPlayer(p_coords.GetCol() + 1);
  int strat = m_table->RowToStrategy(p_coords.GetCol() + 1, p_coords.GetRow());
  Gambit::GameStrategy strategy = support.GetStrategy(player, strat);

  if (support.IsDominated(strategy, false)) {
    wxRect rect = CellToRect(p_coords);
    if (support.IsDominated(strategy, true)) {
      p_dc.SetPen(wxPen(m_doc->GetStyle().GetPlayerColor(player),
                  2, wxSOLID));
    }
    else {
      p_dc.SetPen(wxPen(m_doc->GetStyle().GetPlayerColor(player),
                  1, wxSHORT_DASH));
    }
    p_dc.DrawLine(rect.x, rect.y, rect.x + rect.width, rect.y + rect.height);
    p_dc.DrawLine(rect.x + rect.width, rect.y, rect.x, rect.y + rect.height);
  }
}


void gbtRowPlayerWidget::OnUpdate(void)
{
  int newRows = m_table->NumRowContingencies();
  if (newRows > GetNumberRows())  InsertRows(0, newRows - GetNumberRows());
  if (newRows < GetNumberRows())  DeleteRows(0, GetNumberRows() - newRows);

  int newCols = m_table->NumRowPlayers();
  if (newCols > GetNumberCols())  InsertCols(0, newCols - GetNumberCols());
  if (newCols < GetNumberCols())  DeleteCols(0, GetNumberCols() - newCols);
  if (newCols == 0)  InsertCols(0, 1);

  const Gambit::StrategySupport &support = m_doc->GetNfgSupport();

  for (int col = 0; col < GetNumberCols(); col++) {
    for (int row = 0; row < GetNumberRows(); 
       SetCellSpan(wxSheetCoords(row++, col), wxSheetCoords(1, 1)));

    int span = m_table->NumRowsSpanned(col+1);

    int row = 0;
    while (row < GetNumberRows()) {
      SetCellSpan(wxSheetCoords(row, col), wxSheetCoords(span, 1));
      row += span;
    }
  }

  Refresh();
}

bool gbtRowPlayerWidget::DropText(wxCoord p_x, wxCoord p_y,
                          const wxString &p_text)
{
  if (p_text[0] == 'P') {
    long pl;
    p_text.Right(p_text.Length() - 1).ToLong(&pl);
    
    if (m_table->NumRowPlayers() == 0) {
      m_table->SetRowPlayer(1, pl);
      return true;
    }
    
    for (int col = 0; col < GetNumberCols(); col++) {
      wxRect rect = CellToRect(wxSheetCoords(0, col));

      if (p_x >= rect.x && p_x < rect.x + rect.width/2) {
      m_table->SetRowPlayer(col+1, pl);
      return true;
      }
      else if (p_x >= rect.x + rect.width/2 && p_x < rect.x + rect.width) {
      m_table->SetRowPlayer(col+2, pl);
      return true;
      }
    }
  }

  return false;
}

//=========================================================================
//                       class gbtColPlayerWidget 
//=========================================================================

class gbtColPlayerWidget : public gbtTableWidgetBase {
private:
  gbtGameDocument *m_doc;
  gbtTableWidget *m_table;

  /// @name Overriding wxSheet members for data access
  //@{
  /// Returns the value in the cell
  wxString GetCellValue(const wxSheetCoords &);
  /// Sets the value in the cell, by relabeling the strategy
  void SetCellValue(const wxSheetCoords &, const wxString &);
  /// Returns the attributes of the cell
  wxSheetCellAttr GetAttr(const wxSheetCoords &p_coords, 
                    wxSheetAttr_Type) const;
  //@}

  /// @name Overriding wxSheet members to customize drawing
  //@{
  /// Overrides to draw dominance indicators
  void DrawCell(wxDC &p_dc, const wxSheetCoords &p_coords);
  //@}  

public:
  /// @name Lifecycle
  //@{
  /// Constructor
  gbtColPlayerWidget(gbtTableWidget *p_parent, gbtGameDocument *p_doc);
  //@}

  /// @name Synchronizing with document state
  //@{
  void OnUpdate(void);
  //@}

  /// @name Drop target interaction
  //@{
  /// Called when the drop target receives text. 
  bool DropText(int p_x, int p_y, const wxString &p_text);
  //@}
};

gbtColPlayerWidget::gbtColPlayerWidget(gbtTableWidget *p_parent,
                               gbtGameDocument *p_doc)
  : gbtTableWidgetBase(p_parent, -1), m_doc(p_doc), m_table(p_parent)
{
  CreateGrid(m_table->NumColPlayers(), 0);
  SetRowLabelWidth(1);
  SetColLabelHeight(1);
  SetScrollBarMode(SB_NEVER);
  SetGridLineColour(*wxBLACK);
  SetBackgroundColour(*wxLIGHT_GREY);

  SetDropTarget(new gbtTableWidgetDropTarget(this));
}

void gbtColPlayerWidget::OnUpdate(void)
{
  int newCols = m_table->NumColContingencies() * m_doc->NumPlayers();
  if (newCols > GetNumberCols())  InsertCols(0, newCols - GetNumberCols());
  if (newCols < GetNumberCols())  DeleteCols(0, GetNumberCols() - newCols); 

  int newRows = m_table->NumColPlayers();
  if (newRows > GetNumberRows())  InsertRows(0, newRows - GetNumberRows());
  if (newRows < GetNumberRows())  DeleteRows(0, GetNumberRows() - newRows);
  if (newRows == 0)  InsertRows(0, 1);

  for (int row = 0; row < GetNumberRows(); row++) {
    for (int col = 0; col < GetNumberCols(); 
       SetCellSpan(wxSheetCoords(row, col++), wxSheetCoords(1, 1)));

    int span = m_table->NumColsSpanned(row+1) * m_doc->NumPlayers();

    int col = 0;
    while (col < GetNumberCols()) {
      SetCellSpan(wxSheetCoords(row, col), wxSheetCoords(1, span));
      col += span;
    }      
  }

  Refresh();
}

wxString gbtColPlayerWidget::GetCellValue(const wxSheetCoords &p_coords)
{
  if (IsLabelCell(p_coords)) return wxT("");

  if (m_table->NumColPlayers() == 0)  return wxT("Payoffs");

  const Gambit::StrategySupport &support = m_doc->GetNfgSupport();

  int player = m_table->GetColPlayer(p_coords.GetRow() + 1);
  int strat = m_table->ColToStrategy(p_coords.GetRow() + 1, p_coords.GetCol());

  return wxString(support.GetStrategy(player, strat)->GetLabel().c_str(), 
              *wxConvCurrent);
}

void gbtColPlayerWidget::SetCellValue(const wxSheetCoords &p_coords,
                              const wxString &p_value)
{
  const Gambit::StrategySupport &support = m_doc->GetNfgSupport();

  int player = m_table->GetColPlayer(p_coords.GetRow() + 1);
  int strat = m_table->ColToStrategy(p_coords.GetRow() + 1, p_coords.GetCol());

  support.GetStrategy(player, strat)->SetLabel((const char *) p_value.mb_str());
  m_doc->UpdateViews(GBT_DOC_MODIFIED_LABELS);
}

wxSheetCellAttr gbtColPlayerWidget::GetAttr(const wxSheetCoords &p_coords,
                                  wxSheetAttr_Type) const 
{
  wxSheetCellAttr attr(GetSheetRefData()->m_defaultGridCellAttr);
  attr.SetFont(wxFont(10, wxSWISS, wxNORMAL, wxBOLD));
  attr.SetAlignment(wxALIGN_CENTER, wxALIGN_CENTER);
  attr.SetOrientation(wxHORIZONTAL);
  if (m_table->NumColPlayers() > 0) {
    attr.SetForegroundColour(m_doc->GetStyle().GetPlayerColor(m_table->GetColPlayer(p_coords.GetRow()+1)));
    attr.SetReadOnly(m_doc->IsTree());
  }
  else {
    attr.SetForegroundColour(*wxBLACK);
    attr.SetReadOnly(true);
  }
  attr.SetBackgroundColour(*wxLIGHT_GREY);
  return attr;
}

void gbtColPlayerWidget::DrawCell(wxDC &p_dc, const wxSheetCoords &p_coords)
{
  gbtTableWidgetBase::DrawCell(p_dc, p_coords);

  if (!m_table->ShowDominance() || IsLabelCell(p_coords) ||
      m_table->NumColPlayers() == 0)  return;

  const Gambit::StrategySupport &support = m_doc->GetNfgSupport();
  int player = m_table->GetColPlayer(p_coords.GetRow() + 1);
  int strat = m_table->ColToStrategy(p_coords.GetRow() + 1, p_coords.GetCol());
  Gambit::GameStrategy strategy = support.GetStrategy(player, strat);

  if (support.IsDominated(strategy, false)) {
    wxRect rect = CellToRect(p_coords);
    if (support.IsDominated(strategy, true)) {
      p_dc.SetPen(wxPen(m_doc->GetStyle().GetPlayerColor(player),
                  2, wxSOLID));
    }
    else {
      p_dc.SetPen(wxPen(m_doc->GetStyle().GetPlayerColor(player),
                  1, wxSHORT_DASH));
    }
    p_dc.DrawLine(rect.x, rect.y, rect.x + rect.width, rect.y + rect.height);
    p_dc.DrawLine(rect.x + rect.width, rect.y, rect.x, rect.y + rect.height);
  }
}

bool gbtColPlayerWidget::DropText(wxCoord p_x, wxCoord p_y,
                          const wxString &p_text)
{
  if (p_text[0] == 'P') {
    long pl;
    p_text.Right(p_text.Length() - 1).ToLong(&pl);

    if (m_table->NumColPlayers() == 0) {
      m_table->SetColPlayer(1, pl);
      return true;
    }
    
    for (int col = 0; col < GetNumberCols(); col++) {
      wxRect rect = CellToRect(wxSheetCoords(col, 0));

      if (p_y >= rect.y && p_y < rect.y + rect.height/2) {
      m_table->SetColPlayer(col+1, pl);
      return true;
      }
      else if (p_y >= rect.y + rect.height/2 && p_y < rect.y + rect.height) {
      m_table->SetColPlayer(col+2, pl);
      return true;
      }
    }
  }

  return false;
}

//=========================================================================
//                       class gbtPayoffsWidget 
//=========================================================================

class gbtPayoffsWidget : public gbtTableWidgetBase {
private:
  gbtGameDocument *m_doc;
  gbtTableWidget *m_table;

  /// @name Overriding wxSheet members for data access
  //@{
  /// Returns the value in the cell
  wxString GetCellValue(const wxSheetCoords &);
  /// Sets the value in the cell, by editing the outcome
  void SetCellValue(const wxSheetCoords &, const wxString &);
  /// Returns the attributes of the cell
  wxSheetCellAttr GetAttr(const wxSheetCoords &p_coords, 
                    wxSheetAttr_Type) const;
  //@}

  /// @name Overriding wxSheet members for drawing behavior
  //@{
  /// Draws dark borders between contingencies
  void DrawCellBorder(wxDC &p_dc, const wxSheetCoords &p_coords);
  /// Overrides to draw dominance indicators
  void DrawCell(wxDC &p_dc, const wxSheetCoords &p_coords);
  //@}

  /// @name Overriding wxSheet members for event-handling behavior
  //@{
  /// Implement custom tab-traversal behavior
  void OnKeyDown(wxKeyEvent &);
  //@}

  /// Maps columns to corresponding player
  int ColToPlayer(int p_col) const;

public:
  gbtPayoffsWidget(gbtTableWidget *p_parent, gbtGameDocument *p_doc);

  /// @name Synchronizing with document state
  //@{
  void OnUpdate(void);
  //@}

  DECLARE_EVENT_TABLE()
};

BEGIN_EVENT_TABLE(gbtPayoffsWidget, gbtTableWidgetBase)
  EVT_KEY_DOWN(gbtPayoffsWidget::OnKeyDown)
END_EVENT_TABLE()

gbtPayoffsWidget::gbtPayoffsWidget(gbtTableWidget *p_parent,
                           gbtGameDocument *p_doc)
  : gbtTableWidgetBase(p_parent, -1), m_doc(p_doc), m_table(p_parent)
{
  CreateGrid(0, 0);
  SetRowLabelWidth(1);
  SetColLabelHeight(1);
}

//
// Payoffs are ordered first by row players (in hierarchical order),
// followed by column players (in hierarchical order)
//
int gbtPayoffsWidget::ColToPlayer(int p_col) const
{
  int index = p_col % m_doc->NumPlayers() + 1;
  if (index <= m_table->NumRowPlayers()) {
    return m_table->GetRowPlayer(index);
  }
  else {
    return m_table->GetColPlayer(index - m_table->NumRowPlayers());
  }
}

void gbtPayoffsWidget::OnUpdate(void)
{
  int newCols = m_table->NumColContingencies() * m_doc->NumPlayers();
  if (newCols > GetNumberCols())  InsertCols(0, newCols - GetNumberCols());
  if (newCols < GetNumberCols())  DeleteCols(0, GetNumberCols() - newCols);

  int newRows = m_table->NumRowContingencies();
  if (newRows > GetNumberRows())  InsertRows(0, newRows - GetNumberRows());
  if (newRows < GetNumberRows())  DeleteRows(0, GetNumberRows() - newRows);

  Refresh();
}

wxString gbtPayoffsWidget::GetCellValue(const wxSheetCoords &p_coords)
{
  if (IsLabelCell(p_coords))  return wxT("");

  Gambit::PureStrategyProfile profile = m_table->CellToProfile(p_coords);
  int player = ColToPlayer(p_coords.GetCol());
  return wxString(profile.GetPayoff<std::string>(player).c_str(), *wxConvCurrent);
}

void gbtPayoffsWidget::SetCellValue(const wxSheetCoords &p_coords,
                            const wxString &p_value)
{
  Gambit::PureStrategyProfile profile = m_table->CellToProfile(p_coords);
  Gambit::GameOutcome outcome = profile.GetOutcome();
  if (!outcome) {
    outcome = m_doc->GetGame()->NewOutcome();
    profile.SetOutcome(outcome);
  }
  int player = ColToPlayer(p_coords.GetCol());
  outcome->SetPayoff(player, (const char *) p_value.mb_str());
  m_doc->UpdateViews(GBT_DOC_MODIFIED_PAYOFFS);
}

wxSheetCellAttr gbtPayoffsWidget::GetAttr(const wxSheetCoords &p_coords,
                                wxSheetAttr_Type) const 
{
  if (IsLabelCell(p_coords)) {
    wxSheetCellAttr attr(GetSheetRefData()->m_defaultColLabelAttr);
    attr.SetBackgroundColour(*wxLIGHT_GREY);
    return attr;
  }

  wxSheetCellAttr attr(GetSheetRefData()->m_defaultGridCellAttr);
  attr.SetFont(wxFont(10, wxSWISS, wxNORMAL, wxBOLD));
  attr.SetAlignment(wxALIGN_CENTER, wxALIGN_CENTER);
  attr.SetOrientation(wxHORIZONTAL);
  int player = ColToPlayer(p_coords.GetCol());
  attr.SetForegroundColour(m_doc->GetStyle().GetPlayerColor(player));
  attr.SetRenderer(wxSheetCellRenderer(new gbtRationalRendererRefData()));
  attr.SetEditor(wxSheetCellEditor(new gbtRationalEditorRefData()));
  attr.SetReadOnly(m_doc->IsTree());
  return attr;
}

void gbtPayoffsWidget::DrawCellBorder(wxDC &p_dc, 
                              const wxSheetCoords &p_coords)
{
  gbtTableWidgetBase::DrawCellBorder(p_dc, p_coords);

  wxRect rect(CellToRect(p_coords));
  if (rect.width < 1 || rect.height < 1)  return;

  p_dc.SetPen(wxPen(*wxBLACK, 1, wxSOLID));

  // Draw the dark border to the right of the last column of a contingency
  if ((p_coords.GetCol() + 1) % m_doc->GetGame()->NumPlayers() == 0) {
    p_dc.DrawLine(rect.x + rect.width, rect.y,
              rect.x + rect.width, rect.y + rect.height + 1);
  }

  // Draw the bottom border -- currently always dark
  p_dc.DrawLine(rect.x - 1,          rect.y + rect.height,
            rect.x + rect.width, rect.y + rect.height);

  // Draw the top border for the first row
  if (p_coords.GetRow() == 0) {
    p_dc.DrawLine(rect.x - 1, rect.y, rect.x + rect.width, rect.y);
  }
}

void gbtPayoffsWidget::DrawCell(wxDC &p_dc, const wxSheetCoords &p_coords)
{
  gbtTableWidgetBase::DrawCell(p_dc, p_coords);

  if (!m_table->ShowDominance() || IsLabelCell(p_coords)) return;

  Gambit::PureStrategyProfile profile = m_table->CellToProfile(p_coords);
  int player = ColToPlayer(p_coords.GetCol());
  const Gambit::StrategySupport &support = m_doc->GetNfgSupport();

  if (support.IsDominated(profile.GetStrategy(player), false)) {
    wxRect rect = CellToRect(p_coords);
    if (support.IsDominated(profile.GetStrategy(player), true)) {
      p_dc.SetPen(wxPen(m_doc->GetStyle().GetPlayerColor(player),
                  2, wxSOLID));
    }
    else {
      p_dc.SetPen(wxPen(m_doc->GetStyle().GetPlayerColor(player),
                  1, wxSHORT_DASH));
    }
    p_dc.DrawLine(rect.x, rect.y, rect.x + rect.width, rect.y + rect.height);
    p_dc.DrawLine(rect.x + rect.width, rect.y, rect.x, rect.y + rect.height);
  }
}

//!
//! Overriding default wxSheet behavior: when editing, accepting the
//! edited value via the TAB key automatically moves the cursor to
//! the right *and* creates the editor in the next cell.  In addition,
//! tabbing off the rightmost cell entry automatically "wraps" to the
//! next row.
//!
void gbtPayoffsWidget::OnKeyDown(wxKeyEvent &p_event)
{
  if (GetNumberRows() && GetNumberCols()) {
    switch (p_event.GetKeyCode()) {
    case WXK_TAB: {
      if (IsCellEditControlCreated()) {
      DisableCellEditControl(true); 

      int newRow = GetGridCursorRow(), newCol = GetGridCursorCol();

      if (p_event.ShiftDown()) {
        newCol--;
        if (newCol < 0) {
          newCol = GetNumberCols() - 1;
          newRow--;
          if (newRow < 0) {
            newRow = GetNumberRows() - 1;
          }
        }
      }
      else {
        newCol++;
        if (newCol >= GetNumberCols()) {
          newCol = 0;
          newRow++;
          if (newRow >= GetNumberRows()) {
            newRow = 0;
          }
        }
      }
      SetGridCursorCell(wxSheetCoords(newRow, newCol));
      MakeCellVisible(GetGridCursorCell());
      EnableCellEditControl(GetGridCursorCell());
      }
      break;
    }
    default:
      p_event.Skip();
    }
  }
}

//=========================================================================
//                       gbtTableWidget: Lifecycle
//=========================================================================

gbtTableWidget::gbtTableWidget(gbtNfgPanel *p_parent, wxWindowID p_id,
                         gbtGameDocument *p_doc)
  : wxPanel(p_parent, p_id), m_doc(p_doc), m_nfgPanel(p_parent)
{
  m_rowPlayers.Append(1);
  m_colPlayers.Append(2);

  m_payoffSheet = new gbtPayoffsWidget(this, p_doc);
  m_payoffSheet->SetGridLineColour(*wxWHITE);
  m_rowSheet = new gbtRowPlayerWidget(this, p_doc);
  m_colSheet = new gbtColPlayerWidget(this, p_doc);

  wxFlexGridSizer *topSizer = new wxFlexGridSizer(2, 2);
  topSizer->AddGrowableRow(1);
  topSizer->AddGrowableCol(1);
  topSizer->Add(new wxPanel(this, -1));
  topSizer->Add(m_colSheet, 1, wxEXPAND, 0);
  topSizer->Add(m_rowSheet, 1, wxEXPAND, 0);
  topSizer->Add(m_payoffSheet, 1, wxEXPAND, 0);
  
  SetSizer(topSizer);
  Layout();

  Connect(m_colSheet->GetId(), wxEVT_SHEET_VIEW_CHANGED,
        (wxObjectEventFunction) (wxEventFunction) wxStaticCastEvent(wxSheetEventFunction, wxSheetEventFunction(&gbtTableWidget::OnRowSheetScroll)));

  Connect(m_colSheet->GetId(), wxEVT_SHEET_VIEW_CHANGED,
        (wxObjectEventFunction) (wxEventFunction) wxStaticCastEvent(wxSheetEventFunction, wxSheetEventFunction(&gbtTableWidget::OnColSheetScroll)));

  Connect(m_payoffSheet->GetId(), wxEVT_SHEET_VIEW_CHANGED,
        (wxObjectEventFunction) (wxEventFunction) wxStaticCastEvent(wxSheetEventFunction, wxSheetEventFunction(&gbtTableWidget::OnPayoffScroll)));

  Connect(m_rowSheet->GetId(), wxEVT_SHEET_ROW_SIZE,
        (wxObjectEventFunction) (wxEventFunction) wxStaticCastEvent(wxSheetEventFunction, wxSheetEventFunction(&gbtTableWidget::OnRowSheetRow)));

  Connect(m_payoffSheet->GetId(), wxEVT_SHEET_ROW_SIZE,
        (wxObjectEventFunction) (wxEventFunction) wxStaticCastEvent(wxSheetEventFunction, wxSheetEventFunction(&gbtTableWidget::OnPayoffRow)));

  Connect(m_colSheet->GetId(), wxEVT_SHEET_COL_SIZE,
        (wxObjectEventFunction) (wxEventFunction) wxStaticCastEvent(wxSheetEventFunction, wxSheetEventFunction(&gbtTableWidget::OnColSheetColumn)));

  Connect(m_payoffSheet->GetId(), wxEVT_SHEET_COL_SIZE,
        (wxObjectEventFunction) (wxEventFunction) wxStaticCastEvent(wxSheetEventFunction, wxSheetEventFunction(&gbtTableWidget::OnPayoffColumn)));

  Connect(m_rowSheet->GetId(), wxEVT_SHEET_COL_SIZE,
        (wxObjectEventFunction) (wxEventFunction) wxStaticCastEvent(wxSheetEventFunction, wxSheetEventFunction(&gbtTableWidget::OnRowSheetColumn)));

  Connect(m_colSheet->GetId(), wxEVT_SHEET_ROW_SIZE,
        (wxObjectEventFunction) (wxEventFunction) wxStaticCastEvent(wxSheetEventFunction, wxSheetEventFunction(&gbtTableWidget::OnColSheetRow)));

  Connect(m_rowSheet->GetId(), wxEVT_SHEET_EDITOR_ENABLED,
        (wxObjectEventFunction) (wxEventFunction) wxStaticCastEvent(wxSheetEventFunction, wxSheetEventFunction(&gbtTableWidget::OnBeginEdit)));   
  Connect(m_colSheet->GetId(), wxEVT_SHEET_EDITOR_ENABLED,
        (wxObjectEventFunction) (wxEventFunction) wxStaticCastEvent(wxSheetEventFunction, wxSheetEventFunction(&gbtTableWidget::OnBeginEdit)));   
  Connect(m_payoffSheet->GetId(), wxEVT_SHEET_EDITOR_ENABLED,
        (wxObjectEventFunction) (wxEventFunction) wxStaticCastEvent(wxSheetEventFunction, wxSheetEventFunction(&gbtTableWidget::OnBeginEdit)));   
}

//!
//! Scroll row and column label sheets to match payoff sheet origin.
//! Note that wxSheet uses coordinates of -1 to indicate no scroll.
//!
//@{
00847 void gbtTableWidget::OnRowSheetScroll(wxSheetEvent &)
{
  m_payoffSheet->SetGridOrigin(-1, m_rowSheet->GetGridOrigin().y);
}

00852 void gbtTableWidget::OnColSheetScroll(wxSheetEvent &)
{
  m_payoffSheet->SetGridOrigin(m_colSheet->GetGridOrigin().x, -1);
}

00857 void gbtTableWidget::OnPayoffScroll(wxSheetEvent &)
{
  m_colSheet->SetGridOrigin(m_payoffSheet->GetGridOrigin().x, 0);
  m_rowSheet->SetGridOrigin(0, m_payoffSheet->GetGridOrigin().y);
}
//@}

//!
//! These keep the row heights synchronized
//!
//@{
00868 void gbtTableWidget::OnRowSheetRow(wxSheetEvent &p_event)
{
  int height = m_rowSheet->GetRowHeight(p_event.GetRow());
  m_payoffSheet->SetDefaultRowHeight(height, true);
  m_payoffSheet->AdjustScrollbars();
  m_payoffSheet->Refresh();
  m_rowSheet->SetDefaultRowHeight(height, true);
  m_rowSheet->Refresh();
}

00878 void gbtTableWidget::OnPayoffRow(wxSheetEvent &p_event)
{
  int height = m_payoffSheet->GetRowHeight(p_event.GetRow());
  m_payoffSheet->SetDefaultRowHeight(height, true);
  m_payoffSheet->AdjustScrollbars();
  m_payoffSheet->Refresh();
  m_rowSheet->SetDefaultRowHeight(height, true);
  m_rowSheet->Refresh();
}
//@}

//!
//! These keep the column widths synchronized
//!
//@{
00893 void gbtTableWidget::OnColSheetColumn(wxSheetEvent &p_event)
{
  int width = m_colSheet->GetColWidth(p_event.GetCol());
  m_payoffSheet->SetDefaultColWidth(width, true);
  m_payoffSheet->AdjustScrollbars();
  m_payoffSheet->Refresh();
  m_colSheet->SetDefaultColWidth(width, true);
  m_colSheet->Refresh();
}

00903 void gbtTableWidget::OnPayoffColumn(wxSheetEvent &p_event)
{
  int width = m_payoffSheet->GetColWidth(p_event.GetCol());
  m_payoffSheet->SetDefaultColWidth(width, true);
  m_payoffSheet->AdjustScrollbars();
  m_payoffSheet->Refresh();
  m_colSheet->SetDefaultColWidth(width, true);
  m_colSheet->Refresh();
}
//@}

//!
//! These handle correctly sizing the label windows
//!
//@{
00918 void gbtTableWidget::OnRowSheetColumn(wxSheetEvent &p_event)
{
  m_rowSheet->SetDefaultColWidth(m_rowSheet->GetColWidth(p_event.GetCol()),
                         true);
  GetSizer()->Layout();
}

00925 void gbtTableWidget::OnColSheetRow(wxSheetEvent &p_event)
{
  m_colSheet->SetDefaultRowHeight(m_colSheet->GetRowHeight(p_event.GetRow()),
                          true);
  GetSizer()->Layout();
}
//@}

//!
//! This alerts the document to have any other windows post their pending
//! edits.
//!
00937 void gbtTableWidget::OnBeginEdit(wxSheetEvent &)
{
  m_doc->PostPendingChanges();
}

00942 void gbtTableWidget::OnUpdate(void)
{
  if (m_doc->NumPlayers() > m_rowPlayers.Length() + m_colPlayers.Length()) {
    for (int pl = 1; pl <= m_doc->NumPlayers(); pl++) {
      if (!m_rowPlayers.Contains(pl) && !m_colPlayers.Contains(pl)) {
      m_rowPlayers.Append(pl);
      }
    }
  }
  else if (m_doc->NumPlayers() < m_rowPlayers.Length() + m_colPlayers.Length()) {
    for (int i = 1; i <= m_rowPlayers.Length(); i++) {
      if (m_rowPlayers[i] > m_doc->NumPlayers()) {
      m_rowPlayers.Remove(i--);
      }
    }

    for (int i = 1; i <= m_colPlayers.Length(); i++) {
      if (m_colPlayers[i] > m_doc->NumPlayers()) {
      m_colPlayers.Remove(i--);
      }
    }
  }

  dynamic_cast<gbtPayoffsWidget *>(m_payoffSheet)->OnUpdate();

  // We add margins to the player labels to match the scrollbars,
  // so scrolling matches up
  m_colSheet->SetMargins(dynamic_cast<gbtPayoffsWidget *>(m_payoffSheet)->GetVerticalScrollBar()->GetSize().GetWidth(), -1);
  m_rowSheet->SetMargins(-1, dynamic_cast<gbtPayoffsWidget *>(m_payoffSheet)->GetHorizontalScrollBar()->GetSize().GetHeight());

  dynamic_cast<gbtRowPlayerWidget *>(m_rowSheet)->OnUpdate();
  dynamic_cast<gbtColPlayerWidget *>(m_colSheet)->OnUpdate();
  GetSizer()->Layout();
}

00977 void gbtTableWidget::PostPendingChanges(void)
{
  if (m_payoffSheet->IsCellEditControlShown()) {
    m_payoffSheet->DisableCellEditControl(true);
  }

  if (m_rowSheet->IsCellEditControlShown()) {
    m_rowSheet->DisableCellEditControl(true);
  }

  if (m_colSheet->IsCellEditControlShown()) {
    m_colSheet->DisableCellEditControl(true);
  }
}

00992 bool gbtTableWidget::ShowDominance(void) const
{
  return m_nfgPanel->IsDominanceShown();
}

//=========================================================================
//                      gbtTableWidget: View state
//=========================================================================

01001 void gbtTableWidget::SetRowPlayer(int index, int pl)
{
  if (m_rowPlayers.Contains(pl)) {
    int oldIndex = m_rowPlayers.Find(pl);
    m_rowPlayers.Remove(oldIndex);
    if (index > oldIndex) {
      m_rowPlayers.Insert(pl, index-1);
    }
    else {
      m_rowPlayers.Insert(pl, index);
    }
  }
  else {
    m_colPlayers.Remove(m_colPlayers.Find(pl));
    m_rowPlayers.Insert(pl, index);
  }
  OnUpdate();
}

01020 int gbtTableWidget::NumRowContingencies(void) const
{
  int ncont = 1;
  const Gambit::StrategySupport &support = m_doc->GetNfgSupport();
  for (int i = 1; i <= NumRowPlayers(); 
       ncont *= support.NumStrats(GetRowPlayer(i++)));
  return ncont;
}

01029 int gbtTableWidget::NumRowsSpanned(int index) const
{
  int ncont = 1;
  const Gambit::StrategySupport &support = m_doc->GetNfgSupport();
  for (int i = index + 1; i <= NumRowPlayers(); 
       ncont *= support.NumStrats(GetRowPlayer(i++)));
  return ncont;
}

01038 int gbtTableWidget::RowToStrategy(int player, int row) const
{
  int strat = row / NumRowsSpanned(player);
  return (strat % m_doc->GetNfgSupport().NumStrats(GetRowPlayer(player)) + 1);
}

01044 void gbtTableWidget::SetColPlayer(int index, int pl)
{
  if (m_colPlayers.Contains(pl)) {
    int oldIndex = m_colPlayers.Find(pl);
    m_colPlayers.Remove(oldIndex);
    if (index > oldIndex) {
      m_colPlayers.Insert(pl, index-1);
    }
    else {
      m_colPlayers.Insert(pl, index);
    }
  }
  else {
    m_rowPlayers.Remove(m_rowPlayers.Find(pl));
    m_colPlayers.Insert(pl, index);
  }
  OnUpdate();
}

01063 int gbtTableWidget::NumColContingencies(void) const
{
  int ncont = 1;
  const Gambit::StrategySupport &support = m_doc->GetNfgSupport();
  for (int i = 1; i <= NumColPlayers(); 
       ncont *= support.NumStrats(GetColPlayer(i++)));
  return ncont;
}

01072 int gbtTableWidget::NumColsSpanned(int index) const
{
  int ncont = 1;
  const Gambit::StrategySupport &support = m_doc->GetNfgSupport();
  for (int i = index + 1; i <= NumColPlayers(); 
       ncont *= support.NumStrats(GetColPlayer(i++)));
  return ncont;
}

01081 int gbtTableWidget::ColToStrategy(int player, int col) const
{
  int strat = col / m_doc->NumPlayers() / NumColsSpanned(player);
  return (strat % m_doc->GetNfgSupport().NumStrats(GetColPlayer(player)) + 1);
}

Gambit::PureStrategyProfile 
01088 gbtTableWidget::CellToProfile(const wxSheetCoords &p_coords) const
{
  const Gambit::StrategySupport &support = m_doc->GetNfgSupport();

  Gambit::PureStrategyProfile profile(m_doc->GetGame());
  for (int i = 1; i <= NumRowPlayers(); i++) {
    int player = GetRowPlayer(i);
    profile.SetStrategy(support.GetStrategy(player,
                                  RowToStrategy(i, p_coords.GetRow())));
  }

  for (int i = 1; i <= NumColPlayers(); i++) {
    int player = GetColPlayer(i);
    profile.SetStrategy(support.GetStrategy(player,
                                  ColToStrategy(i, p_coords.GetCol())));
  }

  return profile;
}

class gbtNfgPrintout : public wxPrintout {
private:
  gbtTableWidget *m_table;

public:
  gbtNfgPrintout(gbtTableWidget *p_table, const wxString &p_label)
    : wxPrintout(p_label), m_table(p_table) { }
  virtual ~gbtNfgPrintout() { }

  bool OnPrintPage(int) 
  { m_table->RenderGame(*GetDC(), 50, 50);  return true; }
  bool HasPage(int page) { return (page <= 1); }
  void GetPageInfo(int *minPage, int *maxPage,
               int *selPageFrom, int *selPageTo)
  { *minPage = 1; *maxPage = 1; *selPageFrom = 1; *selPageTo = 1; }
};

01125 wxPrintout *gbtTableWidget::GetPrintout(void)
{
  return new gbtNfgPrintout(this,
                      wxString(m_doc->GetGame()->GetTitle().c_str(),
                             *wxConvCurrent));
}

01132 wxBitmap gbtTableWidget::GetBitmap(int p_marginX, int p_marginY)
{
  int width = (m_rowSheet->CellToRect(wxSheetCoords(0, m_rowSheet->GetNumberCols() - 1)).GetRight() +
             m_colSheet->CellToRect(wxSheetCoords(0, m_colSheet->GetNumberCols() - 1)).GetRight() +
             2 * p_marginX);
  int height = (m_rowSheet->CellToRect(wxSheetCoords(m_rowSheet->GetNumberRows() - 1, 0)).GetBottom() +
            m_colSheet->CellToRect(wxSheetCoords(m_colSheet->GetNumberRows() - 1, 0)).GetBottom() +
            2 * p_marginY);

  if (width > 65000 || height > 65000) {
    // This is just too huge to export to graphics
    return wxNullBitmap;
  }

  wxMemoryDC dc;
  wxBitmap bitmap(width, height);
  dc.SelectObject(bitmap);
  dc.Clear();
  RenderGame(dc, p_marginX, p_marginY);
  return bitmap;
}

01154 void gbtTableWidget::GetSVG(const wxString &p_filename,
                      int p_marginX, int p_marginY)
{
  int width = (m_rowSheet->CellToRect(wxSheetCoords(0, m_rowSheet->GetNumberCols() - 1)).GetRight() +
             m_colSheet->CellToRect(wxSheetCoords(0, m_colSheet->GetNumberCols() - 1)).GetRight() +
             2 * p_marginX);
  int height = (m_rowSheet->CellToRect(wxSheetCoords(m_rowSheet->GetNumberRows() - 1, 0)).GetBottom() +
            m_colSheet->CellToRect(wxSheetCoords(m_colSheet->GetNumberRows() - 1, 0)).GetBottom() +
            2 * p_marginY);

  wxSVGFileDC dc(p_filename, width, height);
  // For some reason, this needs to be initialized
  dc.SetLogicalScale(1.0, 1.0);
  RenderGame(dc, p_marginX, p_marginY);
}

01170 void gbtTableWidget::RenderGame(wxDC &p_dc, int p_marginX, int p_marginY)
{
  // The size of the image to be drawn
  int maxX = (m_rowSheet->CellToRect(wxSheetCoords(0, m_rowSheet->GetNumberCols() - 1)).GetRight() +
            m_colSheet->CellToRect(wxSheetCoords(0, m_colSheet->GetNumberCols() - 1)).GetRight());
  int maxY = (m_rowSheet->CellToRect(wxSheetCoords(m_rowSheet->GetNumberRows() - 1, 0)).GetBottom() +
            m_colSheet->CellToRect(wxSheetCoords(m_colSheet->GetNumberRows() - 1, 0)).GetBottom());

  // Get the size of the DC in pixels
  wxCoord w, h;
  p_dc.GetSize(&w, &h);

  // Calculate a scaling factor
  double scaleX = (double) w / (double) (maxX + 2*p_marginX);
  double scaleY = (double) h / (double) (maxY + 2*p_marginY);
  double scale = (scaleX < scaleY) ? scaleX : scaleY;
  // Here, zooming in is often a good idea, since the number of pixels
  // on a page is generally quite large
  p_dc.SetUserScale(scale, scale);

  // Calculate the position on the DC to center the tree
  double posX = (double) ((w - (maxX * scale)) / 2.0);
  double posY = (double) ((h - (maxY * scale)) / 2.0);

  // The X and Y coordinates of the upper left of the payoff table
  int payoffX = (int) (m_rowSheet->CellToRect(wxSheetCoords(0, m_rowSheet->GetNumberCols() - 1)).GetRight() * scale);
  int payoffY = (int) (m_colSheet->CellToRect(wxSheetCoords(m_colSheet->GetNumberRows() - 1, 0)).GetBottom() * scale);

  p_dc.SetDeviceOrigin((int) posX, payoffY + (int) posY);
  m_rowSheet->DrawGridCells(p_dc,
                      wxSheetBlock(0, 0, 
                               m_rowSheet->GetNumberRows(),
                               m_rowSheet->GetNumberCols()));

  p_dc.SetDeviceOrigin(payoffX + (int) posX, (int) posY);
  m_colSheet->DrawGridCells(p_dc,
                      wxSheetBlock(0, 0, 
                               m_colSheet->GetNumberRows(),
                               m_colSheet->GetNumberCols()));

  p_dc.SetDeviceOrigin(payoffX + (int) posX, payoffY + (int) posY);
  m_payoffSheet->DrawGridCells(p_dc,
                         wxSheetBlock(0, 0,
                                  m_payoffSheet->GetNumberRows(),
                                  m_payoffSheet->GetNumberCols()));
}


Generated by  Doxygen 1.6.0   Back to index