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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -471,8 +471,12 @@ gambit_SOURCES = \
src/gui/analysis.h \
src/gui/app.cc \
src/gui/app.h \
src/gui/editlabel.cc \
src/gui/editlabel.h \
src/gui/edittext.cc \
src/gui/edittext.h \
src/gui/labelcell.cc \
src/gui/labelcell.h \
src/gui/dlabout.cc \
src/gui/dlabout.h \
src/gui/dleditmove.cc \
Expand Down
29 changes: 16 additions & 13 deletions src/gui/dleditmove.cc
Original file line number Diff line number Diff line change
Expand Up @@ -30,20 +30,21 @@
#include "gambit.h"
#include "dleditmove.h"
#include "valnumber.h"
#include "editlabel.h"

namespace Gambit::GUI {

class ActionPanel final : public wxScrolledWindow {
std::vector<wxString> m_actionProbValues;
std::vector<wxTextCtrl *> m_actionNames;
std::vector<LabelTextCtrl *> m_actionLabels;
std::vector<wxTextCtrl *> m_actionProbs;

public:
ActionPanel(wxWindow *p_parent, const GameInfoset &p_infoset);

int NumActions() const { return static_cast<int>(m_actionNames.size()); }
int NumActions() const { return static_cast<int>(m_actionLabels.size()); }

wxString GetActionName(int p_act) const;
wxString GetActionLabel(int p_act) const;
Array<Number> GetActionProbs() const;
};

Expand All @@ -57,7 +58,7 @@ ActionPanel::ActionPanel(wxWindow *p_parent, const GameInfoset &p_infoset)
m_actionProbValues.reserve(p_infoset->GetActions().size());
m_actionProbs.reserve(p_infoset->GetActions().size());
}
m_actionNames.reserve(p_infoset->GetActions().size());
m_actionLabels.reserve(p_infoset->GetActions().size());

const int numColumns = isChance ? 3 : 2;

Expand All @@ -82,8 +83,9 @@ ActionPanel::ActionPanel(wxWindow *p_parent, const GameInfoset &p_infoset)
wxALIGN_CENTER_VERTICAL | wxALIGN_RIGHT);

auto *name =
new wxTextCtrl(this, wxID_ANY, wxString(action->GetLabel().c_str(), *wxConvCurrent));
m_actionNames.push_back(name);
new LabelTextCtrl(this, wxID_ANY, wxString(action->GetLabel().c_str(), *wxConvCurrent),
LabelCharacterPolicy::AsciiOnly);
m_actionLabels.push_back(name);
gridSizer->Add(name, 1, wxEXPAND);

if (isChance) {
Expand All @@ -110,9 +112,9 @@ ActionPanel::ActionPanel(wxWindow *p_parent, const GameInfoset &p_infoset)
SetMinSize(wxSize(FromDIP(isChance ? 400 : 300), std::min(bestSize.GetHeight(), FromDIP(250))));
}

wxString ActionPanel::GetActionName(int p_act) const
wxString ActionPanel::GetActionLabel(int p_act) const
{
return m_actionNames.at(p_act - 1)->GetValue();
return m_actionLabels.at(p_act - 1)->GetNormalizedValue();
}

Array<Number> ActionPanel::GetActionProbs() const
Expand All @@ -138,9 +140,10 @@ EditMoveDialog::EditMoveDialog(wxWindow *p_parent, const GameInfoset &p_infoset)
auto *labelSizer = new wxBoxSizer(wxHORIZONTAL);
labelSizer->Add(new wxStaticText(this, wxID_STATIC, _("Information set label")), 0,
wxALL | wxALIGN_CENTER_VERTICAL, 5);
m_infosetName =
new wxTextCtrl(this, wxID_ANY, wxString(p_infoset->GetLabel().c_str(), *wxConvCurrent));
labelSizer->Add(m_infosetName, 1, wxALL | wxEXPAND, 5);
m_infosetLabel =
new LabelTextCtrl(this, wxID_ANY, wxString(p_infoset->GetLabel().c_str(), *wxConvCurrent),
LabelCharacterPolicy::AsciiOnly);
labelSizer->Add(m_infosetLabel, 1, wxALL | wxEXPAND, 5);
topSizer->Add(labelSizer, 0, wxEXPAND);

{
Expand Down Expand Up @@ -215,9 +218,9 @@ void EditMoveDialog::OnOK(wxCommandEvent &p_event)

int EditMoveDialog::NumActions() const { return m_actionPanel->NumActions(); }

wxString EditMoveDialog::GetActionName(int p_act) const
wxString EditMoveDialog::GetActionLabel(int p_act) const
{
return m_actionPanel->GetActionName(p_act);
return m_actionPanel->GetActionLabel(p_act);
}

Array<Number> EditMoveDialog::GetActionProbs() const { return m_actionPanel->GetActionProbs(); }
Expand Down
8 changes: 5 additions & 3 deletions src/gui/dleditmove.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,15 @@
#ifndef GAMBIT_GUI_DLEDITMOVE_H
#define GAMBIT_GUI_DLEDITMOVE_H

#include "editlabel.h"

namespace Gambit::GUI {
class ActionPanel;

class EditMoveDialog final : public wxDialog {
GameInfoset m_infoset;
wxChoice *m_player;
wxTextCtrl *m_infosetName;
LabelTextCtrl *m_infosetLabel;
ActionPanel *m_actionPanel;

void OnOK(wxCommandEvent &);
Expand All @@ -39,11 +41,11 @@ class EditMoveDialog final : public wxDialog {
EditMoveDialog(wxWindow *p_parent, const GameInfoset &p_infoset);

// Data access (only valid when ShowModal() returns with wxID_OK)
wxString GetInfosetName() const { return m_infosetName->GetValue(); }
wxString GetInfosetLabel() const { return m_infosetLabel->GetNormalizedValue(); }
int GetPlayer() const { return (m_player->GetSelection() + 1); }

int NumActions() const;
wxString GetActionName(int p_act) const;
wxString GetActionLabel(int p_act) const;
Array<Number> GetActionProbs() const;
};
} // namespace Gambit::GUI
Expand Down
6 changes: 3 additions & 3 deletions src/gui/dleditnode.cc
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,9 @@ EditNodeDialog::EditNodeDialog(wxWindow *p_parent, const GameNode &p_node)

auto *labelSizer = new wxBoxSizer(wxHORIZONTAL);
labelSizer->Add(new wxStaticText(this, wxID_STATIC, _("Node label")), 0, wxALL | wxCENTER, 5);
m_nodeName =
new wxTextCtrl(this, wxID_ANY, wxString(m_node->GetLabel().c_str(), *wxConvCurrent));
labelSizer->Add(m_nodeName, 1, wxALL | wxCENTER | wxEXPAND, 5);
m_nodeLabel =
new LabelTextCtrl(this, wxID_ANY, wxString(m_node->GetLabel().c_str(), *wxConvCurrent));
labelSizer->Add(m_nodeLabel, 1, wxALL | wxCENTER | wxEXPAND, 5);
topSizer->Add(labelSizer, 0, wxALL | wxEXPAND, 5);

auto *infosetSizer = new wxBoxSizer(wxHORIZONTAL);
Expand Down
6 changes: 4 additions & 2 deletions src/gui/dleditnode.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,12 @@
#ifndef GAMBIT_GUI_DLEDITNODE_H
#define GAMBIT_GUI_DLEDITNODE_H

#include "editlabel.h"

namespace Gambit::GUI {
class EditNodeDialog final : public wxDialog {
GameNode m_node;
wxTextCtrl *m_nodeName;
LabelTextCtrl *m_nodeLabel;
wxChoice *m_outcome, *m_infoset;
Array<GameInfoset> m_infosetList;

Expand All @@ -35,7 +37,7 @@ class EditNodeDialog final : public wxDialog {
EditNodeDialog(wxWindow *p_parent, const GameNode &p_node);

// Data access (only valid when ShowModal() returns with wxID_OK)
wxString GetNodeName() const { return m_nodeName->GetValue(); }
wxString GetNodeLabel() const { return m_nodeLabel->GetValue(); }
int GetOutcome() const { return m_outcome->GetSelection(); }
GameInfoset GetInfoset() const;
};
Expand Down
146 changes: 146 additions & 0 deletions src/gui/editlabel.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
//
// This file is part of Gambit
// Copyright (c) 1994-2026, The Gambit Project (https://www.gambit-project.org)
//
// FILE: src/gui/editlabel.cc
// Text control for editing valid labels
//
// 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 <algorithm>

#include "editlabel.h"

namespace Gambit::GUI {

bool LabelTextCtrl::IsAsciiPrintable(wxUniChar p_char)
{
const auto value = static_cast<unsigned long>(p_char);
return value >= 0x20 && value <= 0x7e;
}

bool LabelTextCtrl::IsLabelWhitespace(wxUniChar p_char)
{
return p_char == ' ' || p_char == '\t' || p_char == '\r' || p_char == '\n' || p_char == '\v' ||
p_char == '\f';
}

bool LabelTextCtrl::IsAllowedNonWhitespace(wxUniChar p_char, LabelCharacterPolicy p_policy)
{
switch (p_policy) {
case LabelCharacterPolicy::AsciiOnly:
return IsAsciiPrintable(p_char) && !IsLabelWhitespace(p_char);

case LabelCharacterPolicy::Unicode:
return !IsLabelWhitespace(p_char);

default:
return false;
}
}

wxString LabelTextCtrl::Normalize(const wxString &p_value, bool p_stripTrailing,
LabelCharacterPolicy p_policy)
{
wxString normalized;
bool sawNonWhitespace = false;
bool previousWasSpace = false;

for (wxString::const_iterator iter = p_value.begin(); iter != p_value.end(); ++iter) {
const wxUniChar ch = *iter;

if (IsLabelWhitespace(ch)) {
if (!sawNonWhitespace) {
continue;
}
if (!previousWasSpace) {
normalized << ' ';
previousWasSpace = true;
}
continue;
}

if (!IsAllowedNonWhitespace(ch, p_policy)) {
continue;
}

normalized << ch;
sawNonWhitespace = true;
previousWasSpace = false;
}

if (p_stripTrailing && normalized.EndsWith(" ")) {
normalized.RemoveLast();
}

return normalized;
}

void LabelTextCtrl::NormalizeInPlace(bool p_stripTrailing)
{
if (m_normalizing) {
return;
}

const wxString oldValue = GetValue();
const wxString newValue = Normalize(oldValue, p_stripTrailing);
if (oldValue == newValue) {
return;
}

const long insertionPoint = GetInsertionPoint();

m_normalizing = true;
ChangeValue(newValue);
SetInsertionPoint(std::min<long>(insertionPoint, newValue.length()));
m_normalizing = false;
}

void LabelTextCtrl::OnText(wxCommandEvent &p_event)
{
NormalizeInPlace(false);
p_event.Skip();
}

void LabelTextCtrl::OnKillFocus(wxFocusEvent &p_event)
{
NormalizeInPlace(true);
p_event.Skip();
}

LabelTextCtrl::LabelTextCtrl(wxWindow *p_parent, wxWindowID p_id, const wxString &p_value,
LabelCharacterPolicy p_policy, const wxPoint &p_pos,
const wxSize &p_size, long p_style)
: wxTextCtrl(p_parent, p_id, wxEmptyString, p_pos, p_size, p_style), m_policy(p_policy)
{
ChangeValue(Normalize(p_value, true));

Bind(wxEVT_TEXT, &LabelTextCtrl::OnText, this);
Bind(wxEVT_KILL_FOCUS, &LabelTextCtrl::OnKillFocus, this);
}

wxString LabelTextCtrl::GetNormalizedValue()
{
NormalizeInPlace(true);
return GetValue();
}

} // namespace Gambit::GUI
63 changes: 63 additions & 0 deletions src/gui/editlabel.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
//
// This file is part of Gambit
// Copyright (c) 1994-2026, The Gambit Project (https://www.gambit-project.org)
//
// FILE: src/gui/editlabel.h
// Text control for editing valid labels
//
// 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.
//

#ifndef EDITLABEL_H
#define EDITLABEL_H

#include <wx/wxprec.h>
#ifndef WX_PRECOMP
#include <wx/wx.h>
#endif // WX_PRECOMP

namespace Gambit::GUI {

enum class LabelCharacterPolicy { AsciiOnly, Unicode };

class LabelTextCtrl final : public wxTextCtrl {
LabelCharacterPolicy m_policy;
bool m_normalizing{false};

static bool IsAsciiPrintable(wxUniChar p_char);
static bool IsLabelWhitespace(wxUniChar p_char);
static bool IsAllowedNonWhitespace(wxUniChar p_char, LabelCharacterPolicy p_policy);

wxString NormalizeValue(const wxString &p_value, bool p_stripTrailing) const;
void NormalizeInPlace(bool p_stripTrailing);

void OnText(wxCommandEvent &p_event);
void OnKillFocus(wxFocusEvent &p_event);

public:
static wxString Normalize(const wxString &p_value, bool p_stripTrailing,
LabelCharacterPolicy p_policy = LabelCharacterPolicy::AsciiOnly);

LabelTextCtrl(wxWindow *p_parent, wxWindowID p_id, const wxString &p_value,
LabelCharacterPolicy p_policy = LabelCharacterPolicy::AsciiOnly,
const wxPoint &p_pos = wxDefaultPosition, const wxSize &p_size = wxDefaultSize,
long p_style = 0);

wxString GetNormalizedValue();
};

} // namespace Gambit::GUI

#endif // EDITLABEL_H
Loading
Loading