Files
fs2open.github.com/fred2/modifyvariabledlg.cpp
Goober5000 4026d30bdc fix DDV_ validation in FRED, especially DDV_MaxChars
Any DDV (Dialog Data Validation) routine must appear immediately after the field it validates in order for the enforcement to work in real-time.  If the DDV_ routine is not linked to the field, it will perform validation after-the-fact by displaying a dialog box and returning an error.  This is an alternate way of handling validation, but in order for the dialog box not to generate an endless loop, it would be necessary to check the return value of every `UpdateData(TRUE)` call in the FRED codebase (or at least the first `UpdateData(TRUE)` in a pair of them) and then, if the return value is `FALSE`, doing an early return from the enclosing function.

Fixes #6427 as well as all similar cases in FRED.
2024-11-18 23:01:21 -05:00

606 lines
17 KiB
C++

/*
* Copyright (C) Volition, Inc. 1999. All rights reserved.
*
* All source code herein is the property of Volition, Inc. You may not sell
* or otherwise commercially exploit the source or things you created based on the
* source.
*
*/
// ModifyVariableDlg.cpp : implementation file
//
#include "stdafx.h"
#include "FRED.h"
#include "ModifyVariableDlg.h"
#include "parse/sexp.h"
#ifdef _DEBUG
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif
#define NO_RESET_FOCUS 0
#define RESET_FOCUS 1
/////////////////////////////////////////////////////////////////////////////
// CModifyVariableDlg dialog
CModifyVariableDlg::CModifyVariableDlg(CWnd* pParent /*=NULL*/)
: CDialog(CModifyVariableDlg::IDD, pParent)
{
//{{AFX_DATA_INIT(CModifyVariableDlg)
m_default_value = _T("");
m_cur_variable_name = _T("");
//}}AFX_DATA_INIT
}
void CModifyVariableDlg::DoDataExchange(CDataExchange* pDX)
{
CDialog::DoDataExchange(pDX);
//{{AFX_DATA_MAP(CModifyVariableDlg)
DDX_Text(pDX, IDC_MODIFY_DEFAULT_VALUE, m_default_value);
DDV_MaxChars(pDX, m_default_value, TOKEN_LENGTH - 1);
DDX_CBString(pDX, IDC_MODIFY_VARIABLE_NAME, m_cur_variable_name);
DDV_MaxChars(pDX, m_cur_variable_name, TOKEN_LENGTH - 1);
//}}AFX_DATA_MAP
}
BEGIN_MESSAGE_MAP(CModifyVariableDlg, CDialog)
//{{AFX_MSG_MAP(CModifyVariableDlg)
ON_BN_CLICKED(ID_DELETE_VARIABLE, OnDeleteVariable)
ON_BN_CLICKED(IDC_TYPE_STRING, OnTypeString)
ON_BN_CLICKED(IDC_TYPE_NUMBER, OnTypeNumber)
ON_BN_CLICKED(IDC_TYPE_PLAYER_PERSISTENT, OnTypeMissionClose)
ON_BN_CLICKED(IDC_TYPE_CAMPAIGN_PERSISTENT, OnTypeMissionProgress)
ON_BN_CLICKED(IDC_TYPE_NETWORK_VARIABLE, OnTypeNetworkVariable)
ON_BN_CLICKED(IDC_TYPE_ETERNAL, OnTypeEternal)
ON_CBN_SELCHANGE(IDC_MODIFY_VARIABLE_NAME, OnSelchangeModifyVariableName)
ON_CBN_EDITCHANGE(IDC_MODIFY_VARIABLE_NAME, OnEditchangeModifyVariableName)
ON_EN_KILLFOCUS(IDC_MODIFY_DEFAULT_VALUE, OnKillfocusModifyDefaultValue)
ON_CBN_DROPDOWN(IDC_MODIFY_VARIABLE_NAME, OnDropdownModifyVariableName)
//}}AFX_MSG_MAP
END_MESSAGE_MAP()
/////////////////////////////////////////////////////////////////////////////
// CModifyVariableDlg message handlers
// Maybe delete variable
void CModifyVariableDlg::OnDeleteVariable()
{
CString temp_name;
int rval;
// Check for name change
CComboBox *cbox = (CComboBox *) GetDlgItem(IDC_MODIFY_VARIABLE_NAME);
cbox->GetWindowText(temp_name);
// Can't delete. Name has been changed
if ( stricmp(Sexp_variables[get_sexp_var_index()].variable_name, temp_name) ) {
MessageBox("Can not delete variable. Name has been changed.");
return;
}
char message[128] = "Can not delete variable.";
if (!IsChangeSafe(message)) {
return;
}
// maybe delete variable
rval = MessageBox("This will permanantly delete the variable. Do you want to continue?", NULL, MB_OKCANCEL);
if (rval == IDOK) {
// delete variable and exit
m_deleted = true;
// next statement does UpdataData(TRUE);
CDialog::OnOK();
}
}
// Karajorma - Checks whether it is safe to delete or modify this string
bool CModifyVariableDlg::IsChangeSafe(char *message)
{
// Can't as there are SEXPs using it.
int num_counts = m_p_sexp_tree->get_variable_count(Sexp_variables[get_sexp_var_index()].variable_name);
if (num_counts > 0) {
char buffer[256];
sprintf(buffer, "%s Used in %d SEXP(s).", message, num_counts);
MessageBox(buffer);
return false;
}
// Can't as it is used in the team loadout
num_counts = m_p_sexp_tree->get_loadout_variable_count(get_sexp_var_index());
if (num_counts > 0) {
char buffer[256];
sprintf(buffer, "%s Used in %d location(s) in loadout.", message, num_counts);
MessageBox(buffer);
return false;
}
return true;
}
// Set type to string
void CModifyVariableDlg::OnTypeString()
{
// check if type actually modified
if (m_type_number == true) {
// Don't allow type change if in use.
char message[128] = "Can not modify variable type.";
if (!IsChangeSafe(message)) {
m_type_number = true;
m_modified_type = false;
set_variable_type();
return;
}
// keep track if type is really changed
if (m_modified_type == true) {
m_modified_type = false;
} else {
m_modified_type = true;
}
}
m_type_number = false;
set_variable_type();
}
// Set type to number
void CModifyVariableDlg::OnTypeNumber()
{
// check if type actually modified
if (m_type_number == false) {
// Don't allow type change if in use.
char message[128] = "Can not modify variable type.";
if (!IsChangeSafe(message)) {
m_type_number = false;
m_modified_type = false;
set_variable_type();
return;
}
// keep track if type is really changed
if (m_modified_type == true) {
m_modified_type = false;
} else {
m_modified_type = true;
}
}
m_type_number = true;
set_variable_type();
}
// set player persistence
void CModifyVariableDlg::OnTypeMissionClose()
{
m_type_on_mission_close = ((CButton *) GetDlgItem(IDC_TYPE_PLAYER_PERSISTENT))->GetCheck() ? true : false;
// keep track if type is really changed (ugh - just force it to be always changed)
m_modified_persistence = true;
if (m_type_on_mission_close)
m_type_on_mission_progress = false;
// if the variable isn't persistent, it can't be eternal
if (!m_type_on_mission_close && !m_type_on_mission_progress) {
m_type_eternal = false;
}
set_variable_type();
}
// set campaign persistence
void CModifyVariableDlg::OnTypeMissionProgress()
{
m_type_on_mission_progress = ((CButton *) GetDlgItem(IDC_TYPE_CAMPAIGN_PERSISTENT))->GetCheck() ? true : false;
// keep track if type is really changed (ugh - just force it to be always changed)
m_modified_persistence = true;
if (m_type_on_mission_progress)
m_type_on_mission_close = false;
// if the variable isn't persistent, it can't be eternal
if (!m_type_on_mission_close && !m_type_on_mission_progress) {
m_type_eternal = false;
}
set_variable_type();
}
// set eternal persistence
void CModifyVariableDlg::OnTypeEternal()
{
m_type_eternal = ((CButton *)GetDlgItem(IDC_TYPE_ETERNAL))->GetCheck() ? true : false;
m_modified_persistence = true;
// if the variable isn't persistent, it can't be eternal
if (!m_type_on_mission_close && !m_type_on_mission_progress) {
m_type_eternal = false;
MessageBox("Eternal variables must have a different persistence type set first!");
}
set_variable_type();
}
void CModifyVariableDlg::OnTypeNetworkVariable()
{
m_type_network_variable = ((CButton *) GetDlgItem(IDC_TYPE_NETWORK_VARIABLE))->GetCheck() ? true : false;
m_modified_type = true;
set_variable_type();
}
void CModifyVariableDlg::OnSelchangeModifyVariableName()
{
CComboBox *cbox = (CComboBox *) GetDlgItem(IDC_MODIFY_VARIABLE_NAME);
// get index of current selection
int index = cbox->GetCurSel();
// check an item was actually selected, and not outside the box
if (index == CB_ERR) {
return;
}
// check if another has been modified
if (m_modified_type || m_modified_persistence || m_modified_name || m_modified_value) {
// Don't send message if changing to self
if (index != m_combo_last_modified_index) {
MessageBox("Can only modify one variable.");
}
//reset focus to current
cbox->SetCurSel(m_combo_last_modified_index);
return;
}
m_combo_last_modified_index = index;
// Get index into sexp_variables
int sexp_variable_index = get_sexp_var_index();
// Set new type for selection
m_type_number = ((Sexp_variables[sexp_variable_index].type & SEXP_VARIABLE_NUMBER) != 0) ? true : false;
m_type_on_mission_progress = ((Sexp_variables[sexp_variable_index].type & SEXP_VARIABLE_SAVE_ON_MISSION_PROGRESS) != 0) ? true : false;
m_type_on_mission_close = ((Sexp_variables[sexp_variable_index].type & SEXP_VARIABLE_SAVE_ON_MISSION_CLOSE) != 0) ? true : false;
m_type_network_variable = ((Sexp_variables[sexp_variable_index].type & SEXP_VARIABLE_NETWORK) != 0) ? true : false;
m_type_eternal = ((Sexp_variables[sexp_variable_index].type & SEXP_VARIABLE_SAVE_TO_PLAYER_FILE) != 0) ? true : false;
set_variable_type();
// Set new default value for selection
if (sexp_variable_index > -1) {
CEdit *edit = (CEdit *) GetDlgItem(IDC_MODIFY_DEFAULT_VALUE);
edit->SetWindowText(Sexp_variables[sexp_variable_index].text);
}
}
// Check if variable name has changed from Sexp_variables[].varaible name
void CModifyVariableDlg::OnEditchangeModifyVariableName()
{
// Do string compare to check for change
CString temp_name;
// Get current variable name
CComboBox *cbox = (CComboBox *) GetDlgItem(IDC_MODIFY_VARIABLE_NAME);
cbox->GetWindowText(temp_name);
// Check if variable name is modified
if ( strcmp(Sexp_variables[get_sexp_var_index()].variable_name, temp_name) ) {
m_modified_name = true;
} else {
m_modified_name = false;
}
}
BOOL CModifyVariableDlg::OnInitDialog()
{
CDialog::OnInitDialog();
int i, box_index;
// Init combo box and translation table from combo box to sexp_varaibles
CComboBox *cbox = (CComboBox *) GetDlgItem(IDC_MODIFY_VARIABLE_NAME);
cbox->ResetContent();
for (i=0; i<MAX_SEXP_VARIABLES; i++) {
m_translate_combo_to_sexp[i] = -1;
}
// initialize list -- Box is set to *not* sort
for (i=0; i<MAX_SEXP_VARIABLES; i++) {
if ((Sexp_variables[i].type & SEXP_VARIABLE_SET) && !(Sexp_variables[i].type & SEXP_VARIABLE_BLOCK)){
box_index = cbox->AddString(Sexp_variables[i].variable_name);
// check no error
if ( !((box_index == CB_ERR) || (box_index == CB_ERRSPACE)) ) {
m_translate_combo_to_sexp[box_index] = i;
}
}
}
// Exit gracefully if nothing added to combo box
if (cbox->GetCount() == 0) {
Int3(); // this should not happen
OnCancel();
}
int last_modified = 0;
// Set current variable
if (m_start_index > -1) {
for (i=0; i<MAX_SEXP_VARIABLES; i++) {
if (m_translate_combo_to_sexp[i] == m_start_index) {
last_modified = i;
break;
}
}
}
m_combo_last_modified_index = last_modified;
cbox->SetCurSel(last_modified);
// Set the default value
if (m_translate_combo_to_sexp[last_modified] > -1) {
CEdit *edit = (CEdit *) GetDlgItem(IDC_MODIFY_DEFAULT_VALUE);
edit->SetWindowText(Sexp_variables[m_translate_combo_to_sexp[last_modified]].text);
}
// Set old variable name
m_old_var_name = Sexp_variables[m_translate_combo_to_sexp[last_modified]].variable_name;
// Set type
m_type_number = ((Sexp_variables[last_modified].type & SEXP_VARIABLE_NUMBER) != 0) ? true : false;
m_type_on_mission_progress = ((Sexp_variables[last_modified].type & SEXP_VARIABLE_SAVE_ON_MISSION_PROGRESS) != 0) ? true : false;
m_type_on_mission_close = ((Sexp_variables[last_modified].type & SEXP_VARIABLE_SAVE_ON_MISSION_CLOSE) != 0) ? true : false;
m_type_network_variable = ((Sexp_variables[last_modified].type & SEXP_VARIABLE_NETWORK) != 0) ? true : false;
m_type_eternal = ((Sexp_variables[last_modified].type & SEXP_VARIABLE_SAVE_TO_PLAYER_FILE) != 0) ? true : false;
set_variable_type();
// keep track of changes
m_modified_name = false;
m_modified_value = false;
m_modified_type = false;
m_modified_persistence = false;
m_deleted = false;
m_do_modify = false;
m_data_validated = false;
m_var_name_validated = false;
//create tool tip controls
m_ProgressToolTip = new CToolTipCtrl();
m_ProgressToolTip->Create(this);
m_CloseToolTip = new CToolTipCtrl();
m_CloseToolTip->Create(this);
m_EternalToolTip = new CToolTipCtrl();
m_EternalToolTip->Create(this);
CWnd* pWnd = GetDlgItem(IDC_TYPE_CAMPAIGN_PERSISTENT);
m_ProgressToolTip->AddTool(pWnd, "This type of variable will save when the player clicks Accept to go to the next mission");
m_ProgressToolTip->Activate(TRUE);
pWnd = GetDlgItem(IDC_TYPE_PLAYER_PERSISTENT);
m_CloseToolTip->AddTool(pWnd, "This type of variable will save when the player leaves the mission");
m_CloseToolTip->Activate(TRUE);
pWnd = GetDlgItem(IDC_TYPE_ETERNAL);
m_EternalToolTip->AddTool(pWnd, "This type of variable is saved to the player file. So it can be referred to by other campaigns");
m_EternalToolTip->Activate(TRUE);
return TRUE; // return TRUE unless you set the focus to a control
// EXCEPTION: OCX Property Pages should return FALSE
}
BOOL CModifyVariableDlg::PreTranslateMessage(MSG* pMsg) {
m_ProgressToolTip->RelayEvent(pMsg);
m_CloseToolTip->RelayEvent(pMsg);
m_EternalToolTip->RelayEvent(pMsg);
return CDialog::PreTranslateMessage(pMsg);
}
void CModifyVariableDlg::set_variable_type()
{
// get buttons
CButton *button_string = (CButton *) GetDlgItem(IDC_TYPE_STRING);
CButton *button_number = (CButton *) GetDlgItem(IDC_TYPE_NUMBER);
// assign state
button_number->SetCheck( m_type_number);
button_string->SetCheck(!m_type_number);
((CButton *) GetDlgItem(IDC_TYPE_CAMPAIGN_PERSISTENT))->SetCheck(m_type_on_mission_progress);
((CButton *) GetDlgItem(IDC_TYPE_PLAYER_PERSISTENT))->SetCheck(m_type_on_mission_close);
((CButton *) GetDlgItem(IDC_TYPE_NETWORK_VARIABLE))->SetCheck(m_type_network_variable);
((CButton *) GetDlgItem(IDC_TYPE_ETERNAL))->SetCheck(m_type_eternal);
}
void CModifyVariableDlg::OnOK()
{
CString temp_data;
// Validate data
CEdit *edit = (CEdit *) GetDlgItem(IDC_MODIFY_DEFAULT_VALUE);
edit->GetWindowText(temp_data);
// validate data
validate_data(temp_data, RESET_FOCUS);
if (m_data_validated) {
// Don't get OnKillfocusModifyDefaultValue when ok
if (!m_modified_value) {
if ( strcmp(Sexp_variables[get_sexp_var_index()].text, temp_data) ) {
m_modified_value = true;
}
}
// validate variable name
validate_var_name(RESET_FOCUS);
if (m_var_name_validated) {
// maybe set m_do_modify -- this is needed. compare with OnCancel()
if (m_modified_name || m_modified_persistence || m_modified_value || m_modified_type) {
m_do_modify = true;
}
CDialog::OnOK();
}
}
}
void CModifyVariableDlg::OnKillfocusModifyDefaultValue()
{
// Do string compare to check for change
CString temp_data;
CEdit *edit = (CEdit *) GetDlgItem(IDC_MODIFY_DEFAULT_VALUE);
edit->GetWindowText(temp_data);
if ( strcmp(Sexp_variables[get_sexp_var_index()].text, temp_data) ) {
m_modified_value = true;
} else {
m_modified_value = false;
}
}
// validate data
// check (1) zero length (2) invalid chars (3) value if numberf
void CModifyVariableDlg::validate_data(CString &temp_data, int set_focus)
{
// display invalid data message
bool message = false;
char message_text[256];
// check length > 0
auto length = strlen(temp_data);
if (length == 0) {
strcpy_s(message_text, "Invalid Default Value");
message = true;
} else if (m_type_number) {
// check if string and str(atoi(stri)) are same
int temp_num = atoi(temp_data);
char buf[TOKEN_LENGTH];
sprintf(buf, "%d", temp_num);
if ( stricmp(buf, temp_data) ) {
message = true;
strcpy_s(message_text, "Invalid Default Value");
} else {
message = false;
}
}
// check for invalid characters
auto rval = strcspn(temp_data, "@()");
if (rval != length) {
message = true;
sprintf(message_text, "Invalid char '%c' in Default Value", temp_data[(int)rval]);
}
// display message
if ( message ) {
m_data_validated = false;
MessageBox(message_text);
// reset focus
if (set_focus == RESET_FOCUS) {
CEdit *edit = (CEdit *) GetDlgItem(IDC_MODIFY_DEFAULT_VALUE);
edit->SetFocus();
edit->SetSel(0, -1);
}
}
// string always ok, numbers if no message
m_data_validated = !message;
}
// validate variable name
// check (1) zero length (2) invalid chars (3) already in use
void CModifyVariableDlg::validate_var_name(int set_focus)
{
CString temp_name;
CComboBox *cbox = (CComboBox *) GetDlgItem(IDC_MODIFY_VARIABLE_NAME);
cbox->GetWindowText(temp_name);
int cur_sel = cbox->GetCurSel();
m_old_var_name = Sexp_variables[m_translate_combo_to_sexp[cur_sel]].variable_name;
// display invalid data message
bool message = false;
char message_text[256];
// check length > 0
auto length = strlen(temp_name);
if (length == 0) {
strcpy_s(message_text, "Invalid Variable Name");
message = true;
} else {
// check for invalid characters
auto rval = strcspn(temp_name, "@()");
if (rval != length) {
message = true;
sprintf(message_text, "Invalid char '%c' in Variable Name", temp_name[(int)rval]);
} else {
int index = get_index_sexp_variable_name(temp_name);
// if not a new name and not start name
if ( (index != -1) && (index != m_translate_combo_to_sexp[m_combo_last_modified_index]) ) {
message = true;
strcpy_s(message_text, "Variable Name already in use");
}
}
}
// display message
if ( message ) {
MessageBox(message_text);
// reset focus
if (set_focus == RESET_FOCUS) {
cbox->SetFocus();
}
}
// set var_name_validated
m_var_name_validated = !message;
}
int CModifyVariableDlg::get_sexp_var_index()
{
int index = m_translate_combo_to_sexp[m_combo_last_modified_index];
Assert( (index >= 0) && (index < MAX_SEXP_VARIABLES) );
return index;
}
// Reset text in drop down list
void CModifyVariableDlg::OnDropdownModifyVariableName()
{
CString temp_name;
// Get current variable name
CComboBox *cbox = (CComboBox *) GetDlgItem(IDC_MODIFY_VARIABLE_NAME);
cbox->GetWindowText(temp_name);
// Reset combo box text
int rval;
rval = cbox->InsertString(m_combo_last_modified_index, temp_name);
if ( (rval == CB_ERR) || (rval == CB_ERRSPACE) ) {
AfxMessageBox("An internal error has occured.");
OnCancel();
}
cbox->DeleteString(m_combo_last_modified_index+1);
cbox->SetCurSel(m_combo_last_modified_index);
}