// // $Source: /cvsroot/gambit/gambit/sources/gui/efgdisplay.cc,v $ // $Date: 2006/11/11 17:30:24 $ // $Revision: 1.16 $ // // DESCRIPTION: // Implementation of window class to display extensive form tree // // This file is part of Gambit // Copyright (c) 2002, 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 #ifndef WX_PRECOMP #include #endif // WX_PRECOMP #include // for drag-and-drop support #include #include "libgambit/libgambit.h" #include "efgdisplay.h" #include "menuconst.h" //-------------------------------------------------------------------------- // class gbtPayoffEditor //-------------------------------------------------------------------------- BEGIN_EVENT_TABLE(gbtPayoffEditor, wxTextCtrl) EVT_CHAR(gbtPayoffEditor::OnChar) END_EVENT_TABLE() gbtPayoffEditor::gbtPayoffEditor(wxWindow *p_parent) : wxTextCtrl(p_parent, -1, wxT(""), wxDefaultPosition, wxDefaultSize, wxTE_PROCESS_ENTER) { Show(false); } void gbtPayoffEditor::BeginEdit(gbtNodeEntry *p_entry, int p_player) { m_entry = p_entry; m_outcome = p_entry->GetNode()->GetOutcome(); m_player = p_player; SetValue(wxString(m_outcome->GetPayoff(p_player).c_str(), *wxConvCurrent)); SetSelection(-1, -1); Show(true); SetFocus(); } void gbtPayoffEditor::EndEdit(void) { Show(false); } void gbtPayoffEditor::OnChar(wxKeyEvent &p_event) { if (p_event.GetKeyCode() == WXK_TAB) { // We handle the event and pass it to the parent wxPostEvent(GetParent(), p_event); } else { // Default processing p_event.Skip(); } } //-------------------------------------------------------------------------- // Bitmap drawing functions //-------------------------------------------------------------------------- static wxBitmap MakeOutcomeBitmap(void) { wxBitmap bitmap(24, 24); wxMemoryDC dc; dc.SelectObject(bitmap); dc.Clear(); dc.SetPen(wxPen(*wxBLACK, 1, wxSOLID)); // Make a gold-colored background dc.SetBrush(wxBrush(wxColour(255, 215, 0), wxSOLID)); dc.DrawCircle(12, 12, 10); dc.SetFont(wxFont(12, wxSWISS, wxNORMAL, wxBOLD)); dc.SetTextForeground(wxColour(0, 192, 0)); int width, height; dc.GetTextExtent(wxT("u"), &width, &height); dc.DrawText(wxT("u"), 12 - width/2, 12 - height/2); return bitmap; } //-------------------------------------------------------------------------- // class gbtPlayerDropTarget //-------------------------------------------------------------------------- class gbtPlayerDropTarget : public wxTextDropTarget { private: gbtEfgDisplay *m_owner; public: gbtPlayerDropTarget(gbtEfgDisplay *p_owner) { m_owner = p_owner; } bool OnDropText(wxCoord x, wxCoord y, const wxString &p_text); }; // // This recurses the subtree starting at 'p_node' looking for a node // with the ID 'p_id'. // static Gambit::GameNode GetNode(Gambit::GameNode p_node, int p_id) { if (p_node->GetNumber() == p_id) { return p_node; } else if (p_node->NumChildren() == 0) { return 0; } else { for (int i = 1; i <= p_node->NumChildren(); i++) { Gambit::GameNode node = GetNode(p_node->GetChild(i), p_id); if (node) return node; } return 0; } } bool gbtPlayerDropTarget::OnDropText(wxCoord p_x, wxCoord p_y, const wxString &p_text) { Gambit::Game efg = m_owner->GetDocument()->GetGame(); int x, y; #if defined( __WXMSW__) or defined(__WXMAC__) // The +12 here is designed to effectively make the hot spot on // the cursor the center of the cursor image (they're currently // 24 pixels wide). m_owner->CalcUnscrolledPosition(p_x + 12, p_y + 12, &x, &y); #else // Under GTK, there is an angle in the upper left-hand corner which // serves to identify the hot spot. Thus, no adjustment is used m_owner->CalcUnscrolledPosition(p_x, p_y, &x, &y); #endif // __WXMSW__ or defined(__WXMAC__) x = (int) ((float) x / (.01 * m_owner->GetZoom())); y = (int) ((float) y / (.01 * m_owner->GetZoom())); Gambit::GameNode node = m_owner->GetLayout().NodeHitTest(x, y); if (node) { switch (p_text[0]) { case 'P': { long pl; p_text.Right(p_text.Length() - 1).ToLong(&pl); Gambit::GamePlayer player; if (pl == 0) { player = efg->GetChance(); } else { player = efg->GetPlayer(pl); } if (node->NumChildren() == 0) { Gambit::GameInfoset infoset = node->AppendMove(player, 2); infoset->GetAction(1)->SetLabel("1"); infoset->GetAction(2)->SetLabel("2"); } else if (node->GetPlayer() == player) { Gambit::GameAction action = node->GetInfoset()->InsertAction(); action->SetLabel(Gambit::ToText(action->GetNumber())); } else if (!player->IsChance() && !node->GetPlayer()->IsChance()) { // Currently don't support switching nodes to/from chance player node->GetInfoset()->SetPlayer(player); } else { return false; } m_owner->GetDocument()->UpdateViews(GBT_DOC_MODIFIED_GAME); return true; } case 'C': { long n; p_text.Right(p_text.Length() - 1).ToLong(&n); Gambit::GameNode srcNode = GetNode(m_owner->GetDocument()->GetGame()->GetRoot(), n); if (!srcNode) { return false; } if (node->NumChildren() == 0 && srcNode->NumChildren() > 0) { node->CopyTree(srcNode); m_owner->GetDocument()->UpdateViews(GBT_DOC_MODIFIED_GAME); return true; } return false; } case 'M': { long n; p_text.Right(p_text.Length() - 1).ToLong(&n); Gambit::GameNode srcNode = GetNode(efg->GetRoot(), n); if (!srcNode) { return false; } if (node->NumChildren() == 0 && srcNode->NumChildren() > 0) { node->MoveTree(srcNode); m_owner->GetDocument()->UpdateViews(GBT_DOC_MODIFIED_GAME); return true; } return false; } case 'I': { long n; p_text.Right(p_text.Length() - 1).ToLong(&n); Gambit::GameNode srcNode = GetNode(efg->GetRoot(), n); if (!srcNode) { return false; } if (node->NumChildren() > 0 && node->NumChildren() == srcNode->NumChildren()) { node->SetInfoset(srcNode->GetInfoset()); m_owner->GetDocument()->UpdateViews(GBT_DOC_MODIFIED_GAME); return true; } else if (node->NumChildren() == 0 && srcNode->NumChildren() > 0) { node->AppendMove(srcNode->GetInfoset()); m_owner->GetDocument()->UpdateViews(GBT_DOC_MODIFIED_GAME); return true; } return false; } case 'O': { long n; p_text.Right(p_text.Length() - 1).ToLong(&n); Gambit::GameNode srcNode = GetNode(efg->GetRoot(), n); if (!srcNode || node == srcNode) { return false; } node->SetOutcome(srcNode->GetOutcome()); m_owner->GetDocument()->UpdateViews(GBT_DOC_MODIFIED_PAYOFFS); return true; } case 'o': { long n; p_text.Right(p_text.Length() - 1).ToLong(&n); Gambit::GameNode srcNode = GetNode(efg->GetRoot(), n); if (!srcNode || node == srcNode) { return false; } node->SetOutcome(srcNode->GetOutcome()); srcNode->SetOutcome(0); m_owner->GetDocument()->UpdateViews(GBT_DOC_MODIFIED_PAYOFFS); return true; } case 'p': { long n; p_text.Right(p_text.Length() - 1).ToLong(&n); Gambit::GameNode srcNode = GetNode(efg->GetRoot(), n); if (!srcNode || node == srcNode) { return false; } Gambit::GameOutcome outcome = srcNode->GetGame()->NewOutcome(); outcome->SetLabel("Outcome" + Gambit::ToText(outcome->GetNumber())); for (int pl = 1; pl <= srcNode->GetGame()->NumPlayers(); pl++) { outcome->SetPayoff(pl, srcNode->GetOutcome()->GetPayoff(pl)); } node->SetOutcome(outcome); m_owner->GetDocument()->UpdateViews(GBT_DOC_MODIFIED_PAYOFFS); return true; } } } return false; } //---------------------------------------------------------------------- // gbtEfgDisplay: Member functions //---------------------------------------------------------------------- BEGIN_EVENT_TABLE(gbtEfgDisplay, wxScrolledWindow) EVT_MOTION(gbtEfgDisplay::OnMouseMotion) EVT_LEFT_DOWN(gbtEfgDisplay::OnLeftClick) EVT_LEFT_DCLICK(gbtEfgDisplay::OnLeftDoubleClick) EVT_RIGHT_DOWN(gbtEfgDisplay::OnRightClick) EVT_CHAR(gbtEfgDisplay::OnKeyEvent) END_EVENT_TABLE() //---------------------------------------------------------------------- // gbtEfgDisplay: Constructor and destructor //---------------------------------------------------------------------- gbtEfgDisplay::gbtEfgDisplay(wxWindow *p_parent, gbtGameDocument *p_doc) : wxScrolledWindow(p_parent), gbtGameView(p_doc), m_layout(this, p_doc), m_zoom(100) { SetBackgroundColour(wxColour(250, 250, 250)); m_payoffEditor = new gbtPayoffEditor(this); SetDropTarget(new gbtPlayerDropTarget(this)); MakeMenus(); Connect(m_payoffEditor->GetId(), wxEVT_COMMAND_TEXT_ENTER, wxCommandEventHandler(gbtEfgDisplay::OnAcceptPayoffEdit)); } void gbtEfgDisplay::MakeMenus(void) { m_nodeMenu = new wxMenu; m_nodeMenu->Append(wxID_UNDO, _("&Undo\tCtrl-Z"), _("Undo the last change")); m_nodeMenu->Append(wxID_REDO, _("&Redo\tCtrl-Y"), _("Redo the last undone change")); m_nodeMenu->AppendSeparator(); m_nodeMenu->Append(GBT_MENU_EDIT_INSERT_MOVE, _("&Insert move"), _("Insert a move")); m_nodeMenu->Append(GBT_MENU_EDIT_INSERT_ACTION, _("Insert &action"), _("Insert an action at the current move")); m_nodeMenu->Append(GBT_MENU_EDIT_REVEAL, _("&Reveal"), _("Reveal choice at node")); m_nodeMenu->AppendSeparator(); m_nodeMenu->Append(GBT_MENU_EDIT_DELETE_TREE, _("&Delete subtree"), _("Delete the subtree starting at the selected node")); m_nodeMenu->Append(GBT_MENU_EDIT_DELETE_PARENT, _("Delete &parent"), _("Delete the node directly before the selected node")); m_nodeMenu->Append(GBT_MENU_EDIT_REMOVE_OUTCOME, _("Remove &outcome"), _("Remove the outcome from the selected node")); m_nodeMenu->AppendSeparator(); m_nodeMenu->Append(GBT_MENU_EDIT_NODE, _("&Node properties"), _("Edit properties of the node")); m_nodeMenu->Append(GBT_MENU_EDIT_MOVE, _("&Move properties"), _("Edit properties of the move")); m_nodeMenu->AppendSeparator(); m_nodeMenu->Append(GBT_MENU_EDIT_GAME, _("&Game properties"), _("Edit properties of the game")); } //--------------------------------------------------------------------- // gbtEfgDisplay: Event-hook members //--------------------------------------------------------------------- static Gambit::GameNode PriorSameIset(const Gambit::GameNode &n) { Gambit::GameInfoset iset = n->GetInfoset(); if (!iset) return 0; for (int i = 1; i <= iset->NumMembers(); i++) if (iset->GetMember(i) == n) if (i > 1) return iset->GetMember(i-1); else return 0; return 0; } static Gambit::GameNode NextSameIset(const Gambit::GameNode &n) { Gambit::GameInfoset iset = n->GetInfoset(); if (!iset) return 0; for (int i = 1; i <= iset->NumMembers(); i++) if (iset->GetMember(i) == n) if (i < iset->NumMembers()) return iset->GetMember(i+1); else return 0; return 0; } // // OnKeyEvent -- handle keypress events // Currently we support the following keys: // left arrow: go to parent of current node // right arrow: go to first child of current node // up arrow: go to previous sibling of current node // down arrow: go to next sibling of current node // ALT-up: go to previous member of information set // ALT-down: go to next member of information set // space: ensure the selected node is visible // 'R', 'r': select the root node (and make it visible) // delete: delete the subtree rooted at current node // backspace: delete the parent of the current node // 'M', 'm': edit the move at the current node // 'N', 'n': edit the properties of the current node // escape: cancel edit of payoff // tab: accept edit of payoff, edit next payoff (if any) // void gbtEfgDisplay::OnKeyEvent(wxKeyEvent &p_event) { Gambit::GameNode selectNode = m_doc->GetSelectNode(); if (p_event.GetKeyCode() == 'R' || p_event.GetKeyCode() == 'r') { m_doc->SetSelectNode(m_doc->GetGame()->GetRoot()); EnsureNodeVisible(m_doc->GetSelectNode()); return; } if (m_payoffEditor->IsEditing()) { if (p_event.GetKeyCode() == WXK_ESCAPE) { m_payoffEditor->EndEdit(); return; } else if (p_event.GetKeyCode() == WXK_TAB) { m_payoffEditor->EndEdit(); Gambit::GameOutcome outcome = m_payoffEditor->GetOutcome(); int player = m_payoffEditor->GetPlayer(); outcome->SetPayoff(player, (const char *) m_payoffEditor->GetValue().mb_str()); // When we update views, the node entries get redone... Gambit::GameNode node = m_payoffEditor->GetEntry()->GetNode(); m_doc->UpdateViews(GBT_DOC_MODIFIED_PAYOFFS); // Payoff rectangles are actually set during drawing, so // force a refresh wxClientDC dc(this); PrepareDC(dc); OnDraw(dc); if (player < m_doc->NumPlayers()) { gbtNodeEntry *entry = m_layout.GetNodeEntry(node); wxRect rect = entry->GetPayoffExtent(player + 1); int xx, yy; CalcScrolledPosition((int) (.01 * (rect.x - 3) * m_zoom), (int) (.01 * (rect.y - 3) * m_zoom), &xx, &yy); int width = (int) (.01 * (rect.width + 10) * m_zoom); int height = (int) (.01 * (rect.height + 6) * m_zoom); m_payoffEditor->SetSize(xx, yy, width, height); m_payoffEditor->BeginEdit(entry, player + 1); } return; } } // After this point, all events involve moving relative to selected node. // So if there isn't a selected node, the event doesn't apply if (!selectNode) { p_event.Skip(); return; } switch (p_event.GetKeyCode()) { case 'M': case 'm': { wxCommandEvent event(wxEVT_COMMAND_MENU_SELECTED, GBT_MENU_EDIT_MOVE); wxPostEvent(this, event); return; } case 'N': case 'n': { wxCommandEvent event(wxEVT_COMMAND_MENU_SELECTED, GBT_MENU_EDIT_NODE); wxPostEvent(this, event); return; } case WXK_LEFT: if (selectNode->GetParent()) { m_doc->SetSelectNode(m_layout.GetValidParent(selectNode)->GetNode()); EnsureNodeVisible(m_doc->GetSelectNode()); } return; case WXK_RIGHT: if (m_layout.GetValidChild(selectNode)) { m_doc->SetSelectNode(m_layout.GetValidChild(selectNode)->GetNode()); EnsureNodeVisible(m_doc->GetSelectNode()); } return; case WXK_UP: { Gambit::GameNode prior = ((!p_event.AltDown()) ? m_layout.PriorSameLevel(selectNode) : PriorSameIset(selectNode)); if (prior) { m_doc->SetSelectNode(prior); EnsureNodeVisible(m_doc->GetSelectNode()); } return; } case WXK_DOWN: { Gambit::GameNode next = ((!p_event.AltDown()) ? m_layout.NextSameLevel(selectNode) : NextSameIset(selectNode)); if (next) { m_doc->SetSelectNode(next); EnsureNodeVisible(m_doc->GetSelectNode()); } return; } case WXK_SPACE: EnsureNodeVisible(m_doc->GetSelectNode()); return; case WXK_DELETE: { wxCommandEvent event(wxEVT_COMMAND_MENU_SELECTED, GBT_MENU_EDIT_DELETE_TREE); wxPostEvent(this, event); return; } case WXK_BACK: { wxCommandEvent event(wxEVT_COMMAND_MENU_SELECTED, GBT_MENU_EDIT_DELETE_PARENT); wxPostEvent(this, event); return; } default: // If nothing else applies, let event propagate p_event.Skip(); } } void gbtEfgDisplay::OnAcceptPayoffEdit(wxCommandEvent &) { m_payoffEditor->EndEdit(); Gambit::GameOutcome outcome = m_payoffEditor->GetOutcome(); int player = m_payoffEditor->GetPlayer(); outcome->SetPayoff(player, (const char *) m_payoffEditor->GetValue().mb_str()); m_doc->UpdateViews(GBT_DOC_MODIFIED_PAYOFFS); } //--------------------------------------------------------------------- // gbtEfgDisplay: Implementing gbtGameView members //--------------------------------------------------------------------- void gbtEfgDisplay::PostPendingChanges(void) { // FIXME: Save edit! m_payoffEditor->EndEdit(); } void gbtEfgDisplay::OnUpdate(void) { // First make sure that the selected node is in fact still valid if (m_doc->GetSelectNode()) { gbtNodeEntry *entry = m_layout.GetNodeEntry(m_doc->GetSelectNode()); if (!entry) { m_doc->SetSelectNode(0); } } // Force a rebuild on every change for now. RefreshTree(); Gambit::GameNode selectNode = m_doc->GetSelectNode(); m_nodeMenu->Enable(wxID_UNDO, m_doc->CanUndo()); m_nodeMenu->Enable(wxID_REDO, m_doc->CanRedo()); m_nodeMenu->Enable(GBT_MENU_EDIT_INSERT_MOVE, selectNode); m_nodeMenu->Enable(GBT_MENU_EDIT_INSERT_ACTION, selectNode && selectNode->GetInfoset()); m_nodeMenu->Enable(GBT_MENU_EDIT_REVEAL, selectNode && selectNode->GetInfoset()); m_nodeMenu->Enable(GBT_MENU_EDIT_DELETE_TREE, selectNode && selectNode->NumChildren() > 0); m_nodeMenu->Enable(GBT_MENU_EDIT_DELETE_PARENT, selectNode && selectNode->GetParent()); m_nodeMenu->Enable(GBT_MENU_EDIT_REMOVE_OUTCOME, selectNode && selectNode->GetOutcome()); m_nodeMenu->Enable(GBT_MENU_EDIT_NODE, selectNode); m_nodeMenu->Enable(GBT_MENU_EDIT_MOVE, selectNode && selectNode->GetInfoset()); } //--------------------------------------------------------------------- // gbtEfgDisplay: Drawing functions //--------------------------------------------------------------------- void gbtEfgDisplay::RefreshTree(void) { m_layout.BuildNodeList(m_doc->GetEfgSupport()); m_layout.Layout(m_doc->GetEfgSupport()); Refresh(); } void gbtEfgDisplay::AdjustScrollbarSteps(void) { int width, height; GetClientSize(&width, &height); int scrollX, scrollY; GetViewStart(&scrollX, &scrollY); SetScrollbars(50, 50, (int) (m_layout.MaxX() * (.01 * m_zoom) / 50 + 1), (int) (m_layout.MaxY() * (.01 * m_zoom) / 50 + 1), scrollX, scrollY); } void gbtEfgDisplay::FitZoom(void) { int width, height; GetClientSize(&width, &height); double zoomx = (double) width / (double) m_layout.MaxX(); double zoomy = (double) height / (double) m_layout.MaxY(); zoomx = Gambit::min(zoomx, 1.0); zoomy = Gambit::min(zoomy, 1.0); // never zoom in (only out) m_zoom = int(100.0 * (Gambit::min(zoomx, zoomy) * .9)); AdjustScrollbarSteps(); Refresh(); } void gbtEfgDisplay::SetZoom(int p_zoom) { m_zoom = p_zoom; AdjustScrollbarSteps(); EnsureNodeVisible(m_doc->GetSelectNode()); Refresh(); } void gbtEfgDisplay::OnDraw(wxDC &p_dc) { p_dc.SetUserScale(.01 * m_zoom, .01 * m_zoom); #if !wxCHECK_VERSION(2,7,0) p_dc.BeginDrawing(); #endif p_dc.Clear(); int maxX = m_layout.MaxX(); m_layout.Render(p_dc, false); #if !wxCHECK_VERSION(2,7,0) p_dc.EndDrawing(); #endif // When we draw, we might change the location of the right margin // (because of the outcome labels). Make sure scrollbars are adjusted // to reflect this. if (maxX != m_layout.MaxX()) { AdjustScrollbarSteps(); } } void gbtEfgDisplay::OnDraw(wxDC &p_dc, double p_zoom) { // Bit of a hack: this allows us to set zoom separately in printout code int saveZoom = m_zoom; m_zoom = int(100.0 * p_zoom); p_dc.SetUserScale(.01 * m_zoom, .01 * m_zoom); #if !wxCHECK_VERSION(2,7,0) p_dc.BeginDrawing(); #endif p_dc.Clear(); int maxX = m_layout.MaxX(); // A second hack: this is usually only called by functions for hardcopy // output (printouts or graphics images). We want to suppress the // use of the "hints" for these. // FIXME: Of course, this hack implies some useful refactor is called for! m_layout.Render(p_dc, true); #if !wxCHECK_VERSION(2,7,0) p_dc.EndDrawing(); #endif // When we draw, we might change the location of the right margin // (because of the outcome labels). Make sure scrollbars are adjusted // to reflect this. if (maxX != m_layout.MaxX()) { AdjustScrollbarSteps(); } m_zoom = saveZoom; } void gbtEfgDisplay::EnsureNodeVisible(Gambit::GameNode p_node) { if (!p_node) return; gbtNodeEntry *entry = m_layout.GetNodeEntry(p_node); int xScroll, yScroll; GetViewStart(&xScroll, &yScroll); int width, height; GetClientSize(&width, &height); int xx, yy; CalcScrolledPosition((int) (entry->X() * (.01 * m_zoom) - 20), (int) (entry->Y() * (.01 * m_zoom)), &xx, &yy); if (xx < 0) { xScroll -= -xx / 50 + 1; } CalcScrolledPosition((int) (entry->X() * (.01 * m_zoom)), (int) (entry->Y() * (.01 * m_zoom)), &xx, &yy); if (xx > width) { xScroll += (xx - width) / 50 + 1; } if (xScroll < 0) { xScroll = 0; } else if (xScroll > GetScrollRange(wxHORIZONTAL)) { xScroll = GetScrollRange(wxHORIZONTAL); } CalcScrolledPosition((int) (entry->X() * (.01 * m_zoom)), (int) (entry->Y() * (.01 * m_zoom) - 20), &xx, &yy); if (yy < 0) { yScroll -= -yy / 50 + 1; } CalcScrolledPosition((int) (entry->X() * (.01 * m_zoom)), (int) (entry->Y() * (.01 * m_zoom) + 20), &xx, &yy); if (yy > height) { yScroll += (yy - height) / 50 + 1; } if (yScroll < 0) { yScroll = 0; } else if (yScroll > GetScrollRange(wxVERTICAL)) { yScroll = GetScrollRange(wxVERTICAL); } Scroll(xScroll, yScroll); } // // Left mouse button click: // Without key modifiers, selects a node // With shift key, selects whole subtree (not yet implemented) // With control key, adds node to selection (not yet implemented) // void gbtEfgDisplay::OnLeftClick(wxMouseEvent &p_event) { int x, y; CalcUnscrolledPosition(p_event.GetX(), p_event.GetY(), &x, &y); x = (int) ((float) x / (.01 * m_zoom)); y = (int) ((float) y / (.01 * m_zoom)); Gambit::GameNode node = m_layout.NodeHitTest(x, y); if (node != m_doc->GetSelectNode()) { m_doc->SetSelectNode(node); } } // // Left mouse button double-click: // Sets selection, brings up node properties dialog // void gbtEfgDisplay::OnLeftDoubleClick(wxMouseEvent &p_event) { int x, y; CalcUnscrolledPosition(p_event.GetX(), p_event.GetY(), &x, &y); x = (int) ((float) x / (.01 * m_zoom)); y = (int) ((float) y / (.01 * m_zoom)); Gambit::GameNode node = m_layout.NodeHitTest(x, y); if (node) { m_doc->SetSelectNode(node); wxCommandEvent event(wxEVT_COMMAND_MENU_SELECTED, GBT_MENU_EDIT_NODE); wxPostEvent(this, event); return; } node = m_layout.OutcomeHitTest(x, y); if (node) { if (!node->GetOutcome()) { // Create a new outcome node->SetOutcome(m_doc->GetGame()->NewOutcome()); m_doc->UpdateViews(GBT_DOC_MODIFIED_PAYOFFS); // Payoff rectangles are actually set during drawing, so // force a refresh wxClientDC dc(this); PrepareDC(dc); OnDraw(dc); gbtNodeEntry *entry = m_layout.GetNodeEntry(node); wxRect rect = entry->GetPayoffExtent(1); int xx, yy; CalcScrolledPosition((int) (.01 * (rect.x - 3) * m_zoom), (int) (.01 * (rect.y - 3) * m_zoom), &xx, &yy); int width = (int) (.01 * (rect.width + 10) * m_zoom); int height = (int) (.01 * (rect.height + 6) * m_zoom); m_payoffEditor->SetSize(xx, yy, width, height); m_payoffEditor->BeginEdit(entry, 1); return; } // Editing an existing outcome gbtNodeEntry *entry = m_layout.GetNodeEntry(node); for (int pl = 1; pl <= m_doc->NumPlayers(); pl++) { wxRect rect = entry->GetPayoffExtent(pl); #if wxCHECK_VERSION(2,7,0) if (rect.Contains(x, y)) { #else if (rect.Inside(x, y)) { #endif int xx, yy; CalcScrolledPosition((int) (.01 * (rect.x - 3) * m_zoom), (int) (.01 * (rect.y - 3) * m_zoom), &xx, &yy); int width = (int) (.01 * (rect.width + 10) * m_zoom); int height = (int) (.01 * (rect.height + 6) * m_zoom); m_payoffEditor->SetSize(xx, yy, width, height); m_payoffEditor->BeginEdit(entry, pl); return; } } return; } if (m_doc->GetStyle().BranchAboveLabel() == GBT_BRANCH_LABEL_LABEL) { node = m_layout.BranchAboveHitTest(x, y); if (node) { m_doc->SetSelectNode(node); wxCommandEvent event(wxEVT_COMMAND_MENU_SELECTED, GBT_MENU_EDIT_MOVE); wxPostEvent(this, event); return; } } if (m_doc->GetStyle().BranchBelowLabel() == GBT_BRANCH_LABEL_LABEL) { node = m_layout.BranchBelowHitTest(x, y); if (node) { m_doc->SetSelectNode(node); wxCommandEvent event(wxEVT_COMMAND_MENU_SELECTED, GBT_MENU_EDIT_MOVE); wxPostEvent(this, event); return; } } } #include "bitmaps/tree.xpm" #include "bitmaps/move.xpm" void gbtEfgDisplay::OnMouseMotion(wxMouseEvent &p_event) { if (p_event.LeftIsDown() && p_event.Dragging()) { int x, y; CalcUnscrolledPosition(p_event.GetX(), p_event.GetY(), &x, &y); x = (int) ((float) x / (.01 * GetZoom())); y = (int) ((float) y / (.01 * GetZoom())); Gambit::GameNode node = m_layout.NodeHitTest(x, y); if (node && node->NumChildren() > 0) { Gambit::GamePlayer player = node->GetPlayer(); wxString label; if (p_event.ShiftDown()) { label = wxT("i"); } else { label = wxString(player->GetLabel().c_str(), *wxConvCurrent); } if (p_event.ControlDown()) { // Copy subtree wxBitmap bitmap(tree_xpm); #if defined( __WXMSW__) or defined(__WXMAC__) wxImage image = bitmap.ConvertToImage(); #else wxIcon image; image.CopyFromBitmap(bitmap); #endif // _WXMSW__ wxTextDataObject textData(wxString::Format(wxT("C%d"), node->GetNumber())); wxDropSource source(textData, this, image, image, image); /*wxDragResult result =*/ source.DoDragDrop(true); } else if (p_event.ShiftDown()) { // Copy move (information set) // This should be the pawn icon! wxBitmap bitmap(move_xpm); #if defined( __WXMSW__) or defined(__WXMAC__) wxImage image = bitmap.ConvertToImage(); #else wxIcon image; image.CopyFromBitmap(bitmap); #endif // _WXMSW__ wxTextDataObject textData(wxString::Format(wxT("I%d"), node->GetNumber())); wxDropSource source(textData, this, image, image, image); /*wxDragResult result =*/ source.DoDragDrop(wxDrag_DefaultMove); } else { // Move subtree wxBitmap bitmap(tree_xpm); #if defined( __WXMSW__) or defined(__WXMAC__) wxImage image = bitmap.ConvertToImage(); #else wxIcon image; image.CopyFromBitmap(bitmap); #endif // _WXMSW__ wxTextDataObject textData(wxString::Format(wxT("M%d"), node->GetNumber())); wxDropSource source(textData, this, image, image, image); /*wxDragResult result =*/ source.DoDragDrop(wxDrag_DefaultMove); } return; } node = m_layout.OutcomeHitTest(x, y); if (node && node->GetOutcome()) { wxBitmap bitmap = MakeOutcomeBitmap(); #if defined( __WXMSW__) or defined(__WXMAC__) wxImage image = bitmap.ConvertToImage(); #else wxIcon image; image.CopyFromBitmap(bitmap); #endif // _WXMSW__ if (p_event.ControlDown()) { wxTextDataObject textData(wxString::Format(wxT("O%d"), node->GetNumber())); wxDropSource source(textData, this, image, image, image); /*wxDragResult result =*/ source.DoDragDrop(true); } else if (p_event.ShiftDown()) { wxTextDataObject textData(wxString::Format(wxT("p%d"), node->GetNumber())); wxDropSource source(textData, this, image, image, image); /*wxDragResult result =*/ source.DoDragDrop(true); } else { wxTextDataObject textData(wxString::Format(wxT("o%d"), node->GetNumber())); wxDropSource source(textData, this, image, image, image); /*wxDragResult result =*/ source.DoDragDrop(wxDrag_DefaultMove); } } } } // // Right mouse-button click: // Set selection, display context-sensitive popup menu // void gbtEfgDisplay::OnRightClick(wxMouseEvent &p_event) { int x, y; CalcUnscrolledPosition(p_event.GetX(), p_event.GetY(), &x, &y); x = (int) ((float) x / (.01 * m_zoom)); y = (int) ((float) y / (.01 * m_zoom)); Gambit::GameNode node = m_layout.NodeHitTest(x, y); if (node != m_doc->GetSelectNode()) { m_doc->SetSelectNode(node); } PopupMenu(m_nodeMenu); }