mirror of
https://github.com/scp-fs2open/fs2open.github.com.git
synced 2026-11-10 04:31:34 -10:00
984 lines
29 KiB
C++
984 lines
29 KiB
C++
/*
|
|
* Created by Hassan "Karajorma" Kazmi and Josh "jg18" Glatt for The FreeSpace 2 Source Code Project.
|
|
* You may not sell or otherwise commercially exploit the source or things you
|
|
* create based on the source.
|
|
*/
|
|
|
|
// AddModifyContainerDlg.cpp : implementation file
|
|
//
|
|
|
|
#include "stdafx.h"
|
|
#include "FRED.h"
|
|
#include "AddModifyContainerDlg.h"
|
|
#include "parse/sexp.h"
|
|
#include "EditContainerNameDlg.h"
|
|
|
|
#ifdef _DEBUG
|
|
#undef THIS_FILE
|
|
static char THIS_FILE[] = __FILE__;
|
|
#endif
|
|
|
|
#define NO_RESET_FOCUS 0
|
|
#define RESET_FOCUS 1
|
|
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
// AddModifyContainerDlg dialog
|
|
|
|
namespace {
|
|
const SCP_string NEW_CONTAINER_NAME = "<New Container Name>";
|
|
} // namespace
|
|
|
|
CAddModifyContainerDlg::CAddModifyContainerDlg(const sexp_tree &s_tree, CWnd *pParent /*=nullptr*/)
|
|
: CDialog(CAddModifyContainerDlg::IDD, pParent), m_sexp_tree(s_tree)
|
|
{
|
|
}
|
|
|
|
void CAddModifyContainerDlg::DoDataExchange(CDataExchange *pDX)
|
|
{
|
|
CDialog::DoDataExchange(pDX);
|
|
DDX_Control(pDX, IDC_CONTAINER_DATA_LISTER, m_container_data_lister);
|
|
}
|
|
|
|
// see https://stackoverflow.com/questions/17828258/how-to-prevent-mfc-dialog-closing-on-enter-and-escape-keys
|
|
// This is needed because despite the override of the normal OK and Cancel routes, Enter will still be recognized
|
|
BOOL CAddModifyContainerDlg::PreTranslateMessage(MSG* pMsg)
|
|
{
|
|
if (pMsg->message == WM_KEYDOWN)
|
|
{
|
|
if (pMsg->wParam == VK_RETURN || pMsg->wParam == VK_ESCAPE)
|
|
{
|
|
return TRUE; // Do not process further
|
|
}
|
|
}
|
|
return CDialog::PreTranslateMessage(pMsg);
|
|
}
|
|
|
|
BEGIN_MESSAGE_MAP(CAddModifyContainerDlg, CDialog)
|
|
ON_BN_CLICKED(IDC_CONTAINER_ADD, OnContainerAdd)
|
|
ON_BN_CLICKED(IDC_CONTAINER_INSERT, OnContainerInsert)
|
|
ON_BN_CLICKED(IDC_CONTAINER_REMOVE, OnContainerRemove)
|
|
ON_BN_CLICKED(IDC_CONTAINER_UPDATE, OnContainerUpdate)
|
|
|
|
ON_BN_CLICKED(ID_OK, OnButtonOk)
|
|
ON_BN_CLICKED(ID_CANCEL, OnButtonCancel)
|
|
ON_WM_CLOSE()
|
|
|
|
ON_BN_CLICKED(IDC_CONTAINER_LIST, OnTypeList)
|
|
ON_BN_CLICKED(IDC_CONTAINER_MAP, OnTypeMap)
|
|
ON_BN_CLICKED(IDC_CONTAINER_NUMBER_DATA, OnTypeNumber)
|
|
ON_BN_CLICKED(IDC_CONTAINER_STRING_DATA, OnTypeString)
|
|
|
|
ON_LBN_SELCHANGE(IDC_CONTAINER_DATA_LISTER, OnListerSelectionChange)
|
|
|
|
ON_CBN_SELCHANGE(IDC_CURRENT_CONTAINER_NAME, OnSelchangeContainerName)
|
|
|
|
ON_BN_CLICKED(IDC_CONTAINER_NO_PERSIST, OnPersistNone)
|
|
ON_BN_CLICKED(IDC_CONTAINER_CAMPAIGN_PERSIST, OnSaveOnMissionComplete)
|
|
ON_BN_CLICKED(IDC_CONTAINER_PLAYER_PERSIST, OnSaveOnMissionClose)
|
|
ON_BN_CLICKED(IDC_CONTAINER_ETERNAL_PERSIST, OnPersistEternal)
|
|
|
|
ON_BN_CLICKED(IDC_ADD_NEW_CONTAINER, &CAddModifyContainerDlg::OnBnClickedAddNewContainer)
|
|
ON_BN_CLICKED(IDC_RENAME_CONTAINER, &CAddModifyContainerDlg::OnBnClickedRenameContainer)
|
|
|
|
ON_BN_CLICKED(IDC_CONTAINER_STRING_KEYS, &CAddModifyContainerDlg::OnBnClickedStringKeys)
|
|
ON_BN_CLICKED(IDC_CONTAINER_NUMBER_KEYS, &CAddModifyContainerDlg::OnBnClickedNumberKeys)
|
|
ON_BN_CLICKED(IDC_DELETE_CONTAINER, &CAddModifyContainerDlg::OnBnClickedDeleteContainer)
|
|
END_MESSAGE_MAP()
|
|
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
// CAddModifyContainerDlg message handlers
|
|
|
|
BOOL CAddModifyContainerDlg::OnInitDialog()
|
|
{
|
|
CDialog::OnInitDialog();
|
|
|
|
//grab the existing list of containers and duplicate it. We only update it if the user clicks OK.
|
|
m_containers = get_all_sexp_containers();
|
|
|
|
// init renaming maps
|
|
for (const auto &container : m_containers) {
|
|
const auto &container_name = container.container_name;
|
|
m_old_to_new_names[container_name] = container_name;
|
|
m_new_to_old_names[container_name] = container_name;
|
|
}
|
|
|
|
CComboBox *cbox = (CComboBox *) GetDlgItem(IDC_CURRENT_CONTAINER_NAME);
|
|
cbox->ResetContent();
|
|
|
|
CEdit *key_edit = (CEdit *)GetDlgItem(IDC_CONTAINER_KEY);
|
|
key_edit->SetLimitText(sexp_container::VALUE_MAX_LENGTH);
|
|
CEdit *data_edit = (CEdit *)GetDlgItem(IDC_CONTAINER_DATA);
|
|
data_edit->SetLimitText(sexp_container::VALUE_MAX_LENGTH);
|
|
|
|
// do we already have any containers defined?
|
|
if (has_containers()) {
|
|
cbox->EnableWindow(true);
|
|
for (const auto &container : m_containers) {
|
|
cbox->AddString(container.container_name.c_str());
|
|
}
|
|
m_current_container = 0;
|
|
cbox->SetCurSel(m_current_container);
|
|
GetDlgItem(IDC_RENAME_CONTAINER)->EnableWindow(true);
|
|
GetDlgItem(IDC_DELETE_CONTAINER)->EnableWindow(true);
|
|
} else {
|
|
m_current_container = -1;
|
|
}
|
|
set_selected_container();
|
|
|
|
// Send default name and values into dialog box
|
|
UpdateData(FALSE);
|
|
|
|
return TRUE; // return TRUE unless you set the focus to a control
|
|
// EXCEPTION: OCX Property Pages should return FALSE
|
|
}
|
|
|
|
void CAddModifyContainerDlg::set_selected_container()
|
|
{
|
|
if (m_current_container != -1) {
|
|
CComboBox *cbox = (CComboBox*)GetDlgItem(IDC_CURRENT_CONTAINER_NAME);
|
|
Assert(has_containers());
|
|
Assert(cbox->GetCount() == num_containers());
|
|
Assert(m_current_container < cbox->GetCount());
|
|
cbox->SetCurSel(m_current_container);
|
|
}
|
|
|
|
update_controls();
|
|
set_container_type();
|
|
set_data_type();
|
|
if (get_current_container().is_map()) {
|
|
set_key_type();
|
|
}
|
|
set_persistence_options();
|
|
update_data_lister();
|
|
}
|
|
|
|
void CAddModifyContainerDlg::OnButtonOk()
|
|
{
|
|
populate_renamed_containers();
|
|
update_sexp_containers(m_containers, m_renamed_containers);
|
|
|
|
CDialog::OnOK();
|
|
}
|
|
|
|
void CAddModifyContainerDlg::OnButtonCancel()
|
|
{
|
|
if (query_modified()) {
|
|
int z = MessageBox("Do you want to keep your changes?", "Close", MB_ICONQUESTION | MB_YESNOCANCEL);
|
|
if (z == IDCANCEL) {
|
|
return;
|
|
}
|
|
|
|
if (z == IDYES) {
|
|
OnButtonOk();
|
|
return;
|
|
}
|
|
}
|
|
|
|
CDialog::OnCancel();
|
|
}
|
|
|
|
// this is called when you hit the enter key
|
|
void CAddModifyContainerDlg::OnOK()
|
|
{
|
|
// override MFC default behavior and do nothing
|
|
}
|
|
|
|
// this is called when you hit the escape key
|
|
void CAddModifyContainerDlg::OnCancel()
|
|
{
|
|
// override MFC default behavior and do nothing
|
|
}
|
|
|
|
// this is called when you hit the Close button
|
|
void CAddModifyContainerDlg::OnClose()
|
|
{
|
|
OnButtonCancel();
|
|
}
|
|
|
|
void CAddModifyContainerDlg::OnBnClickedStringKeys()
|
|
{
|
|
auto &container = get_current_container();
|
|
Assert(container.is_map());
|
|
Assert(container.empty());
|
|
if (none(container.type & ContainerType::STRING_KEYS)) {
|
|
container.type &= ~ContainerType::NUMBER_KEYS;
|
|
container.type |= ContainerType::STRING_KEYS;
|
|
}
|
|
}
|
|
|
|
void CAddModifyContainerDlg::OnBnClickedNumberKeys()
|
|
{
|
|
auto &container = get_current_container();
|
|
Assert(container.is_map());
|
|
Assert(container.empty());
|
|
if (none(container.type & ContainerType::NUMBER_KEYS)) {
|
|
container.type &= ~ContainerType::STRING_KEYS;
|
|
container.type |= ContainerType::NUMBER_KEYS;
|
|
}
|
|
}
|
|
|
|
void CAddModifyContainerDlg::OnTypeList()
|
|
{
|
|
auto &container = get_current_container();
|
|
Assert(container.empty());
|
|
if (!container.is_list()) {
|
|
container.type &= ~ContainerType::MAP;
|
|
// removing the "*_KEYS" type flag is unnecessary, because save_containers() won't save it
|
|
// and keeping it improves UX if the user switches back to map later
|
|
container.type |= ContainerType::LIST;
|
|
update_controls();
|
|
}
|
|
}
|
|
|
|
void CAddModifyContainerDlg::OnTypeMap()
|
|
{
|
|
auto &container = get_current_container();
|
|
Assert(container.empty());
|
|
if (!container.is_map()) {
|
|
container.type &= ~ContainerType::LIST;
|
|
container.type |= ContainerType::MAP;
|
|
if (none(container.type & ContainerType::NUMBER_KEYS)) {
|
|
container.type |= ContainerType::STRING_KEYS;
|
|
}
|
|
|
|
update_controls();
|
|
set_key_type();
|
|
}
|
|
}
|
|
|
|
void CAddModifyContainerDlg::set_container_type()
|
|
{
|
|
CButton *button_list = (CButton *) GetDlgItem(IDC_CONTAINER_LIST);
|
|
CButton *button_map = (CButton *) GetDlgItem(IDC_CONTAINER_MAP);
|
|
|
|
const auto &container = get_current_container();
|
|
const bool list_selected = container.is_list();
|
|
const bool map_selected = container.is_map();
|
|
Assert((int)list_selected ^ (int)map_selected);
|
|
|
|
button_list->SetCheck(list_selected);
|
|
button_map->SetCheck(map_selected);
|
|
}
|
|
|
|
void CAddModifyContainerDlg::OnTypeNumber()
|
|
{
|
|
auto &container = get_current_container();
|
|
Assert(container.empty());
|
|
container.type &= ~ContainerType::STRING_DATA;
|
|
container.type |= ContainerType::NUMBER_DATA;
|
|
container.opf_type = OPF_NUMBER;
|
|
}
|
|
|
|
void CAddModifyContainerDlg::OnTypeString()
|
|
{
|
|
auto &container = get_current_container();
|
|
Assert(container.empty());
|
|
container.type &= ~ContainerType::NUMBER_DATA;
|
|
container.type |= ContainerType::STRING_DATA;
|
|
container.opf_type = OPF_STRING;
|
|
}
|
|
|
|
void CAddModifyContainerDlg::set_data_type()
|
|
{
|
|
CButton *button_string = (CButton *) GetDlgItem(IDC_CONTAINER_STRING_DATA);
|
|
CButton *button_number = (CButton *) GetDlgItem(IDC_CONTAINER_NUMBER_DATA);
|
|
|
|
const auto &container = get_current_container();
|
|
const bool number_selected = any(container.type & ContainerType::NUMBER_DATA);
|
|
const bool string_selected = any(container.type & ContainerType::STRING_DATA);
|
|
|
|
button_number->SetCheck(number_selected);
|
|
button_string->SetCheck(string_selected);
|
|
}
|
|
|
|
void CAddModifyContainerDlg::set_key_type()
|
|
{
|
|
CButton *button_string = (CButton *) GetDlgItem(IDC_CONTAINER_STRING_KEYS);
|
|
CButton *button_number = (CButton *) GetDlgItem(IDC_CONTAINER_NUMBER_KEYS);
|
|
|
|
const auto &container = get_current_container();
|
|
Assert(container.is_map());
|
|
const bool number_keys_selected = any(container.type & ContainerType::NUMBER_KEYS);
|
|
const bool string_keys_selected = any(container.type & ContainerType::STRING_KEYS);
|
|
Assert((int)number_keys_selected ^ (int)string_keys_selected);
|
|
|
|
button_number->SetCheck(number_keys_selected);
|
|
button_string->SetCheck(string_keys_selected);
|
|
}
|
|
|
|
void CAddModifyContainerDlg::OnSelchangeContainerName()
|
|
{
|
|
CComboBox *cbox = (CComboBox *) GetDlgItem(IDC_CURRENT_CONTAINER_NAME);
|
|
|
|
const int selected_container = cbox->GetCurSel();
|
|
if (selected_container != m_current_container) {
|
|
m_current_container = selected_container;
|
|
|
|
set_selected_container();
|
|
}
|
|
}
|
|
|
|
bool CAddModifyContainerDlg::is_container_name_in_use(const char *text, bool ignore_current) const
|
|
{
|
|
for (int i = 0; i < num_containers(); ++i) {
|
|
if (ignore_current && (m_current_container == i)) {
|
|
continue;
|
|
} else if (!stricmp(m_containers[i].container_name.c_str(), text)) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool CAddModifyContainerDlg::is_container_name_valid(CString &new_name, bool is_rename)
|
|
{
|
|
if (new_name.IsEmpty()) {
|
|
MessageBox("Container name can't be empty.");
|
|
return false;
|
|
}
|
|
|
|
if (!stricmp(new_name, NEW_CONTAINER_NAME.c_str())) {
|
|
MessageBox("Invalid container name.");
|
|
return false;
|
|
}
|
|
|
|
if (new_name[0] == ' ' || new_name[new_name.GetLength()-1] == ' ') {
|
|
MessageBox("Container names cannot begin or end with spaces.");
|
|
return false;
|
|
}
|
|
|
|
// handle spaces and & chars
|
|
if (strchr(new_name, sexp_container::DELIM) != nullptr || strchr(new_name, ' ') != nullptr) {
|
|
if (is_rename) {
|
|
MessageBox("Container names cannot contain spaces or &.");
|
|
return false;
|
|
} else {
|
|
MessageBox("Container names cannot contain spaces or &. Replacing with hyphens.");
|
|
new_name.Replace((TCHAR)' ', (TCHAR)'-');
|
|
new_name.Replace((TCHAR)sexp_container::DELIM, (TCHAR)'-');
|
|
}
|
|
}
|
|
|
|
if (is_container_name_in_use(LPCTSTR(new_name), is_rename)) {
|
|
MessageBox("Conflicting container name. A container with this name already exists");
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool CAddModifyContainerDlg::is_valid_number(const char *test_str) const
|
|
{
|
|
const int temp_num = atoi(test_str);
|
|
char buf[TOKEN_LENGTH];
|
|
const int ret = snprintf(buf, sizeof(buf), "%d", temp_num);
|
|
if (ret <= 0) {
|
|
return false;
|
|
}
|
|
buf[ret] = '\0';
|
|
return !strcmp(buf, test_str);
|
|
}
|
|
|
|
void CAddModifyContainerDlg::OnListerSelectionChange()
|
|
{
|
|
const int index = m_container_data_lister.GetCurSel();
|
|
|
|
if (index == LB_ERR) {
|
|
// TODO: clear key and data text edit boxes?
|
|
return;
|
|
}
|
|
|
|
Assert(m_container_data_lister.GetCount() > 0);
|
|
Assert(index >= 0 && index < get_current_container().size());
|
|
|
|
const auto &container = get_current_container();
|
|
Assert(!container.empty());
|
|
|
|
if (container.is_list()) {
|
|
update_text_edit_boxes("", m_lister_keys[index]);
|
|
} else if (container.is_map()) {
|
|
const SCP_string &key = m_lister_keys[index];
|
|
update_text_edit_boxes(key, container.map_data.at(key));
|
|
} else {
|
|
UNREACHABLE("Unknown container type %d", (int)container.type);
|
|
}
|
|
}
|
|
|
|
void CAddModifyContainerDlg::OnPersistNone()
|
|
{
|
|
auto &container = get_current_container();
|
|
container.type &= ~(ContainerType::SAVE_ON_MISSION_PROGRESS | ContainerType::SAVE_ON_MISSION_CLOSE |
|
|
ContainerType::SAVE_TO_PLAYER_FILE);
|
|
|
|
CButton *button_eternal = (CButton *)GetDlgItem(IDC_CONTAINER_ETERNAL_PERSIST);
|
|
button_eternal->SetCheck(FALSE);
|
|
button_eternal->EnableWindow(FALSE);
|
|
}
|
|
|
|
void CAddModifyContainerDlg::OnSaveOnMissionComplete()
|
|
{
|
|
auto &container = get_current_container();
|
|
container.type &= ~ContainerType::SAVE_ON_MISSION_CLOSE;
|
|
container.type |= ContainerType::SAVE_ON_MISSION_PROGRESS;
|
|
|
|
CButton *button_eternal = (CButton *)GetDlgItem(IDC_CONTAINER_ETERNAL_PERSIST);
|
|
if (!button_eternal->IsWindowEnabled()) {
|
|
button_eternal->EnableWindow(TRUE);
|
|
}
|
|
}
|
|
|
|
void CAddModifyContainerDlg::OnSaveOnMissionClose()
|
|
{
|
|
auto &container = get_current_container();
|
|
container.type &= ~ContainerType::SAVE_ON_MISSION_PROGRESS;
|
|
container.type |= ContainerType::SAVE_ON_MISSION_CLOSE;
|
|
|
|
CButton *button_eternal = (CButton *)GetDlgItem(IDC_CONTAINER_ETERNAL_PERSIST);
|
|
if (!button_eternal->IsWindowEnabled()) {
|
|
button_eternal->EnableWindow(TRUE);
|
|
}
|
|
}
|
|
|
|
void CAddModifyContainerDlg::OnPersistEternal()
|
|
{
|
|
auto &container = get_current_container();
|
|
Assert(container.is_persistent());
|
|
|
|
CButton *button_eternal = (CButton *)GetDlgItem(IDC_CONTAINER_ETERNAL_PERSIST);
|
|
if (button_eternal->GetCheck()) {
|
|
container.type |= ContainerType::SAVE_TO_PLAYER_FILE;
|
|
} else {
|
|
container.type &= ~ContainerType::SAVE_TO_PLAYER_FILE;
|
|
}
|
|
}
|
|
|
|
void CAddModifyContainerDlg::set_persistence_options()
|
|
{
|
|
CButton *button_no_persist = (CButton *)GetDlgItem(IDC_CONTAINER_NO_PERSIST);
|
|
CButton *button_campaign_persist = (CButton *)GetDlgItem(IDC_CONTAINER_CAMPAIGN_PERSIST);
|
|
CButton *button_player_persist = (CButton *)GetDlgItem(IDC_CONTAINER_PLAYER_PERSIST);
|
|
CButton *button_eternal = (CButton *)GetDlgItem(IDC_CONTAINER_ETERNAL_PERSIST);
|
|
const auto &container = get_current_container();
|
|
|
|
if (container.is_persistent()) {
|
|
button_no_persist->SetCheck(FALSE);
|
|
|
|
if (any(container.type & ContainerType::SAVE_ON_MISSION_PROGRESS)) {
|
|
Assert(none(container.type & ContainerType::SAVE_ON_MISSION_CLOSE));
|
|
button_campaign_persist->SetCheck(TRUE);
|
|
button_player_persist->SetCheck(FALSE);
|
|
} else {
|
|
Assert(any(container.type & ContainerType::SAVE_ON_MISSION_CLOSE));
|
|
button_campaign_persist->SetCheck(FALSE);
|
|
button_player_persist->SetCheck(TRUE);
|
|
}
|
|
|
|
button_eternal->SetCheck(container.is_eternal());
|
|
} else {
|
|
Assert(!container.is_eternal());
|
|
button_no_persist->SetCheck(TRUE);
|
|
button_player_persist->SetCheck(FALSE);
|
|
button_campaign_persist->SetCheck(FALSE);
|
|
button_eternal->SetCheck(FALSE);
|
|
}
|
|
}
|
|
|
|
|
|
void CAddModifyContainerDlg::OnContainerAdd()
|
|
{
|
|
add_container_entry((int)get_current_container().size());
|
|
}
|
|
|
|
void CAddModifyContainerDlg::OnContainerInsert()
|
|
{
|
|
Assert(get_current_container().is_list());
|
|
|
|
const int index = m_container_data_lister.GetCurSel();
|
|
|
|
// Potential TODO: enable Insert button only when a lister entry is selected
|
|
if (index == LB_ERR) {
|
|
MessageBox("Nothing selected! Use Add to append new data");
|
|
return;
|
|
}
|
|
|
|
add_container_entry(index);
|
|
}
|
|
|
|
void CAddModifyContainerDlg::add_container_entry(const int insert_index)
|
|
{
|
|
Assert(has_containers());
|
|
Assert(m_current_container >= 0 && m_current_container < num_containers());
|
|
|
|
if (!edit_boxes_have_valid_data(false)) {
|
|
return;
|
|
}
|
|
|
|
CEdit *data_edit = (CEdit*)GetDlgItem(IDC_CONTAINER_DATA);
|
|
CString data_str;
|
|
data_edit->GetWindowText(data_str);
|
|
|
|
auto &container = get_current_container();
|
|
if (container.is_list()) {
|
|
Assert(insert_index >= 0 && insert_index <= (int)container.size());
|
|
if (insert_index == (int)container.size()) {
|
|
container.list_data.emplace_back(data_str);
|
|
} else {
|
|
auto insert_it = std::next(container.list_data.begin(), insert_index);
|
|
container.list_data.emplace(insert_it, data_str);
|
|
}
|
|
} else if (container.is_map()) {
|
|
CEdit *key_edit = (CEdit*)GetDlgItem(IDC_CONTAINER_KEY);
|
|
CString key_str;
|
|
key_edit->GetWindowText(key_str);
|
|
container.map_data.emplace(key_str, data_str);
|
|
}
|
|
|
|
if (container.size() == 1) {
|
|
update_controls();
|
|
}
|
|
|
|
update_data_lister();
|
|
update_text_edit_boxes("", "");
|
|
}
|
|
|
|
void CAddModifyContainerDlg::OnContainerRemove()
|
|
{
|
|
Assert(has_containers());
|
|
Assert(m_current_container >= 0 && m_current_container < num_containers());
|
|
Assert(!get_current_container().empty());
|
|
|
|
auto &container = get_current_container();
|
|
|
|
const int index = m_container_data_lister.GetCurSel();
|
|
if (index == LB_ERR) {
|
|
MessageBox("Select an item from the list before pressing Remove.");
|
|
return;
|
|
}
|
|
Assert(index >= 0 && index < container.size());
|
|
|
|
if (container.is_list()) {
|
|
container.list_data.erase(std::next(container.list_data.begin(), index));
|
|
} else if (container.is_map()) {
|
|
const auto &key = m_lister_keys[index];
|
|
container.map_data.erase(key);
|
|
}
|
|
|
|
if (container.empty()) {
|
|
update_controls();
|
|
}
|
|
|
|
update_data_lister();
|
|
}
|
|
|
|
void CAddModifyContainerDlg::OnContainerUpdate()
|
|
{
|
|
Assert(has_containers());
|
|
Assert(m_current_container >= 0 && m_current_container < num_containers());
|
|
Assert(!get_current_container().empty());
|
|
|
|
if (!edit_boxes_have_valid_data(true)) {
|
|
return;
|
|
}
|
|
|
|
const int index = m_container_data_lister.GetCurSel();
|
|
|
|
if (index == LB_ERR) {
|
|
MessageBox("Nothing selected! Use Add to enter new data");
|
|
return;
|
|
}
|
|
|
|
auto &container = get_current_container();
|
|
|
|
CEdit *data_edit = (CEdit *) GetDlgItem(IDC_CONTAINER_DATA);
|
|
CString data_str;
|
|
data_edit->GetWindowText(data_str);
|
|
if (container.is_list()) {
|
|
*std::next(container.list_data.begin(), index) = SCP_string(data_str);
|
|
} else if (container.is_map()) {
|
|
CEdit *key_edit = (CEdit*)GetDlgItem(IDC_CONTAINER_KEY);
|
|
CString key_str;
|
|
key_edit->GetWindowText(key_str);
|
|
container.map_data[SCP_string(key_str)] = data_str;
|
|
} else {
|
|
UNREACHABLE("Unknown container type %d", (int)container.type);
|
|
}
|
|
|
|
update_data_lister();
|
|
}
|
|
|
|
bool CAddModifyContainerDlg::edit_boxes_have_valid_data(bool dup_key_ok)
|
|
{
|
|
if (!data_edit_box_has_valid_data()) {
|
|
return false;
|
|
}
|
|
if (get_current_container().is_map() && !key_edit_box_has_valid_data(dup_key_ok)) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool CAddModifyContainerDlg::key_edit_box_has_valid_data(bool dup_ok)
|
|
{
|
|
Assert(has_containers());
|
|
Assert(m_current_container >= 0 && m_current_container < num_containers());
|
|
const auto &container = get_current_container();
|
|
Assert(container.is_map());
|
|
|
|
CEdit *key_edit = (CEdit *) GetDlgItem(IDC_CONTAINER_KEY);
|
|
CString key_str;
|
|
key_edit->GetWindowText(key_str);
|
|
|
|
if (key_str.IsEmpty()) {
|
|
MessageBox("You have not entered a key!");
|
|
return false;
|
|
}
|
|
|
|
if (!dup_ok) {
|
|
const auto &map_data = container.map_data;
|
|
const auto count = std::count_if(map_data.cbegin(),
|
|
map_data.cend(),
|
|
[key_str](const std::pair<SCP_string, SCP_string> &map_entry) -> bool {
|
|
return !stricmp(map_entry.first.c_str(), key_str);
|
|
});
|
|
|
|
if (count > 0) {
|
|
MessageBox("This key already exists! Use Update to change an existing key's data.");
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (any(container.type & ContainerType::NUMBER_KEYS) && !is_valid_number(key_str)) {
|
|
Assert(container.is_map());
|
|
MessageBox("This key is not a valid number and this map container uses numeric keys!");
|
|
return false;
|
|
}
|
|
|
|
// TODO: validate strictly typed keys
|
|
|
|
return true;
|
|
}
|
|
|
|
bool CAddModifyContainerDlg::data_edit_box_has_valid_data()
|
|
{
|
|
CEdit *data_edit = (CEdit *) GetDlgItem(IDC_CONTAINER_DATA);
|
|
CString data_str;
|
|
data_edit->GetWindowText(data_str);
|
|
|
|
if (data_str.IsEmpty()) {
|
|
MessageBox("You have not entered any data!");
|
|
return false;
|
|
}
|
|
|
|
if (any(get_current_container().type & ContainerType::NUMBER_DATA) && !is_valid_number(data_str)) {
|
|
MessageBox("This data is not a valid number and this container uses numeric data!");
|
|
return false;
|
|
}
|
|
|
|
// TODO: validate strictly typed data
|
|
|
|
return true;
|
|
}
|
|
|
|
void CAddModifyContainerDlg::update_controls()
|
|
{
|
|
update_type_controls();
|
|
update_data_entry_controls();
|
|
}
|
|
|
|
void CAddModifyContainerDlg::update_type_controls()
|
|
{
|
|
const auto &container = get_current_container();
|
|
const bool container_empty = container.empty();
|
|
const bool map_selected = container.is_map();
|
|
|
|
// container type
|
|
GetDlgItem(IDC_CONTAINER_LIST)->EnableWindow(container_empty);
|
|
GetDlgItem(IDC_CONTAINER_MAP)->EnableWindow(container_empty);
|
|
|
|
// data type
|
|
GetDlgItem(IDC_CONTAINER_STRING_DATA)->EnableWindow(container_empty);
|
|
GetDlgItem(IDC_CONTAINER_NUMBER_DATA)->EnableWindow(container_empty);
|
|
|
|
// key type
|
|
GetDlgItem(IDC_CONTAINER_STRING_KEYS)->EnableWindow(container_empty && map_selected);
|
|
GetDlgItem(IDC_CONTAINER_NUMBER_KEYS)->EnableWindow(container_empty && map_selected);
|
|
|
|
// persistence
|
|
GetDlgItem(IDC_CONTAINER_ETERNAL_PERSIST)->EnableWindow(container.is_persistent());
|
|
|
|
}
|
|
|
|
void CAddModifyContainerDlg::update_data_entry_controls()
|
|
{
|
|
const bool has_container = has_containers();
|
|
const auto &container = get_current_container();
|
|
const bool container_empty = container.empty();
|
|
const bool map_selected = container.is_map();
|
|
|
|
// data manipulation buttons
|
|
GetDlgItem(IDC_CONTAINER_ADD)->EnableWindow(has_container);
|
|
GetDlgItem(IDC_CONTAINER_INSERT)->EnableWindow(has_container && !map_selected);
|
|
GetDlgItem(IDC_CONTAINER_REMOVE)->EnableWindow(has_container && !container_empty);
|
|
GetDlgItem(IDC_CONTAINER_UPDATE)->EnableWindow(has_container && !container_empty);
|
|
|
|
// text edit boxes
|
|
GetDlgItem(IDC_CONTAINER_KEY)->EnableWindow(has_container && map_selected);
|
|
GetDlgItem(IDC_CONTAINER_DATA)->EnableWindow(has_container);
|
|
}
|
|
|
|
void CAddModifyContainerDlg::update_data_lister()
|
|
{
|
|
m_container_data_lister.ResetContent();
|
|
m_lister_keys.clear();
|
|
|
|
// Data is displayed in one of two ways depending on container type
|
|
const auto &container = get_current_container();
|
|
|
|
if (container.is_list()) {
|
|
for (const auto &list_entry : container.list_data) {
|
|
m_lister_keys.emplace_back(list_entry);
|
|
m_container_data_lister.AddString(list_entry.c_str());
|
|
}
|
|
} else if (container.is_map()) {
|
|
for (const auto &map_entry : container.map_data) {
|
|
m_lister_keys.emplace_back(map_entry.first);
|
|
}
|
|
|
|
if (any(container.type & ContainerType::STRING_KEYS)) {
|
|
std::sort(m_lister_keys.begin(), m_lister_keys.end());
|
|
} else if (any(container.type & ContainerType::NUMBER_KEYS)) {
|
|
std::sort(m_lister_keys.begin(),
|
|
m_lister_keys.end(),
|
|
[](const SCP_string &str1, const SCP_string &str2) -> bool {
|
|
return std::atoi(str1.c_str()) < std::atoi(str2.c_str());
|
|
});
|
|
} else {
|
|
UNREACHABLE("Unknown container type %d", (int)container.type);
|
|
}
|
|
|
|
for (const auto &key : m_lister_keys) {
|
|
const SCP_string lister_entry = key + " - " + container.map_data.at(key);
|
|
m_container_data_lister.AddString(lister_entry.c_str());
|
|
}
|
|
} else {
|
|
UNREACHABLE("Unknown container type %d", (int)container.type);
|
|
}
|
|
|
|
m_container_data_lister.EnableWindow(m_container_data_lister.GetCount() > 0);
|
|
}
|
|
|
|
void CAddModifyContainerDlg::OnBnClickedAddNewContainer()
|
|
{
|
|
CComboBox *cbox = (CComboBox *) GetDlgItem(IDC_CURRENT_CONTAINER_NAME);
|
|
|
|
CEditContainerNameDlg dlg("Add New Container", NEW_CONTAINER_NAME);
|
|
dlg.DoModal();
|
|
|
|
// if we didn't enter a name
|
|
if (dlg.cancelled()) {
|
|
return;
|
|
}
|
|
|
|
CString temp_name = dlg.new_container_name();
|
|
if (!is_container_name_valid(temp_name, false)) {
|
|
return;
|
|
}
|
|
|
|
m_containers.emplace_back();
|
|
auto &new_container = m_containers.back();
|
|
new_container.container_name = temp_name;
|
|
|
|
const auto &container = get_current_container();
|
|
new_container.type = container.type;
|
|
new_container.opf_type = container.opf_type;
|
|
|
|
// add the new container to the list of containers combo box and make it the selected option
|
|
cbox->AddString(temp_name);
|
|
m_current_container = num_containers() - 1;
|
|
|
|
// update renaming maps
|
|
const SCP_string &new_container_name = new_container.container_name;
|
|
const SCP_string new_container_marker = create_null_container_name();
|
|
m_old_to_new_names[new_container_marker] = new_container_name;
|
|
m_new_to_old_names[new_container_name] = new_container_marker;
|
|
|
|
// in case this is the first container
|
|
if (num_containers() == 1) {
|
|
cbox->EnableWindow(true);
|
|
GetDlgItem(IDC_RENAME_CONTAINER)->EnableWindow(true);
|
|
GetDlgItem(IDC_DELETE_CONTAINER)->EnableWindow(true);
|
|
}
|
|
|
|
set_selected_container();
|
|
}
|
|
|
|
void CAddModifyContainerDlg::OnBnClickedRenameContainer()
|
|
{
|
|
Assert(has_containers());
|
|
Assert(m_current_container >= 0);
|
|
Assert(m_current_container < num_containers());
|
|
|
|
CComboBox *cbox = (CComboBox *)GetDlgItem(IDC_CURRENT_CONTAINER_NAME);
|
|
Assert(cbox->IsWindowEnabled());
|
|
Assert(cbox->GetCount() > 0);
|
|
|
|
auto &container = get_current_container();
|
|
|
|
CEditContainerNameDlg dlg("Rename Container", container.container_name);
|
|
dlg.DoModal();
|
|
|
|
if (dlg.cancelled()) {
|
|
return;
|
|
}
|
|
|
|
CString new_name = dlg.new_container_name();
|
|
if (is_container_name_valid(new_name, true)) {
|
|
// update renaming maps
|
|
const SCP_string new_name_str = new_name;
|
|
const SCP_string curr_name = get_current_container().container_name;
|
|
const auto prev_name_it = m_new_to_old_names.find(curr_name);
|
|
Assertion(prev_name_it != m_new_to_old_names.end(),
|
|
"Attempt to rename untracked container %s. Please report!",
|
|
curr_name.c_str());
|
|
const SCP_string prev_name = prev_name_it->second;
|
|
m_new_to_old_names.erase(curr_name);
|
|
m_old_to_new_names[prev_name] = new_name_str;
|
|
m_new_to_old_names[new_name_str] = prev_name;
|
|
|
|
container.container_name = new_name;
|
|
cbox->DeleteString((UINT)m_current_container);
|
|
cbox->InsertString(m_current_container, container.container_name.c_str());
|
|
cbox->SetCurSel(m_current_container);
|
|
}
|
|
}
|
|
|
|
void CAddModifyContainerDlg::OnBnClickedDeleteContainer()
|
|
{
|
|
Assert(has_containers());
|
|
Assert(m_current_container >= 0 && m_current_container < num_containers());
|
|
|
|
char buffer[256];
|
|
|
|
// get both current and original names, even if identical
|
|
const SCP_string curr_name = get_current_container().container_name;
|
|
const auto orig_name_it = m_new_to_old_names.find(curr_name);
|
|
Assertion(orig_name_it != m_new_to_old_names.end(),
|
|
"Attempt to delete untracked container %s. Please report!",
|
|
curr_name.c_str());
|
|
const SCP_string orig_name = orig_name_it->second;
|
|
|
|
const int times_used =
|
|
is_null_container_name(orig_name) ? 0 : m_sexp_tree.get_container_usage_count(orig_name);
|
|
Assertion(times_used >= 0,
|
|
"Checking usage count of container name %s returned count of %d. Please report!",
|
|
orig_name.c_str(),
|
|
times_used);
|
|
|
|
if (times_used > 0) {
|
|
SCP_string container_name_str = "'" + curr_name + "'";
|
|
if (curr_name != orig_name) {
|
|
container_name_str += " (previously '" + orig_name + "')";
|
|
}
|
|
sprintf(buffer,
|
|
"Container %s is currently used %d times. Remove those uses before deleting the container.",
|
|
container_name_str.c_str(),
|
|
times_used);
|
|
MessageBox(buffer);
|
|
return;
|
|
}
|
|
|
|
sprintf(buffer, "Delete SEXP container '%s'?", curr_name.c_str());
|
|
|
|
const int ret = MessageBox(buffer, "Delete?", MB_OKCANCEL | MB_ICONEXCLAMATION);
|
|
if (ret == IDCANCEL) {
|
|
return;
|
|
}
|
|
|
|
// update renaming maps
|
|
m_new_to_old_names.erase(curr_name);
|
|
m_old_to_new_names.erase(orig_name);
|
|
// FIXME TODO: is inserting a tombstone needed?
|
|
// if so, filter them out in populate_renamed_containers()
|
|
//if (!is_null_container_name(orig_name)) {
|
|
// const SCP_string container_tombstone = create_null_container_name();
|
|
// m_old_to_new_names[orig_name] = container_tombstone;
|
|
//}
|
|
|
|
// prevent sudden change in type selection buttons
|
|
if (num_containers() == 1) {
|
|
Assert(m_current_container == 0);
|
|
const auto &last_container = m_containers.front();
|
|
m_dummy_container.type = last_container.type;
|
|
m_dummy_container.opf_type = last_container.opf_type;
|
|
}
|
|
|
|
m_containers.erase(m_containers.begin() + m_current_container);
|
|
|
|
CComboBox *cbox = (CComboBox *) GetDlgItem(IDC_CURRENT_CONTAINER_NAME);
|
|
cbox->DeleteString((int)cbox->GetCurSel());
|
|
|
|
if (m_containers.empty()) {
|
|
m_current_container = -1;
|
|
Assert(cbox->GetCount() == 0);
|
|
cbox->EnableWindow(false);
|
|
GetDlgItem(IDC_RENAME_CONTAINER)->EnableWindow(false);
|
|
GetDlgItem(IDC_DELETE_CONTAINER)->EnableWindow(false);
|
|
} else {
|
|
m_current_container = 0;
|
|
cbox->SetCurSel(m_current_container);
|
|
}
|
|
set_selected_container();
|
|
}
|
|
|
|
void CAddModifyContainerDlg::update_text_edit_boxes(const SCP_string &key, const SCP_string &data)
|
|
{
|
|
CEdit *key_edit = (CEdit*)GetDlgItem(IDC_CONTAINER_KEY);
|
|
key_edit->SetWindowText(key.c_str());
|
|
|
|
CEdit *data_edit = (CEdit*)GetDlgItem(IDC_CONTAINER_DATA);
|
|
data_edit->SetWindowText(data.c_str());
|
|
}
|
|
|
|
sexp_container &CAddModifyContainerDlg::get_current_container()
|
|
{
|
|
Assertion(m_current_container >= -1 && m_current_container < num_containers(),
|
|
"Attempt to access invalid container index %d with %d containers. Please report!",
|
|
m_current_container,
|
|
num_containers());
|
|
return m_current_container < 0 ? m_dummy_container : m_containers[m_current_container];
|
|
}
|
|
|
|
void CAddModifyContainerDlg::populate_renamed_containers()
|
|
{
|
|
for (const auto &old_name_to_new : m_old_to_new_names) {
|
|
const auto &old_name = old_name_to_new.first;
|
|
const auto &new_name = old_name_to_new.second;
|
|
if (!is_null_container_name(old_name) && stricmp(old_name.c_str(), new_name.c_str())) {
|
|
m_renamed_containers.emplace(old_name, new_name);
|
|
}
|
|
}
|
|
}
|
|
|
|
bool CAddModifyContainerDlg::query_modified() const
|
|
{
|
|
return get_all_sexp_containers() != m_containers;
|
|
}
|
|
|
|
// static data/functions
|
|
int CAddModifyContainerDlg::m_null_container_counter = 1;
|
|
|
|
bool CAddModifyContainerDlg::is_null_container_name(const SCP_string &container_name)
|
|
{
|
|
Assertion(!container_name.empty(),
|
|
"Encountered empty container name when checking for null container name. Please report!");
|
|
return container_name[0] == sexp_container::DELIM;
|
|
}
|
|
|
|
SCP_string CAddModifyContainerDlg::create_null_container_name()
|
|
{
|
|
return sexp_container::DELIM_STR + std::to_string(m_null_container_counter++);
|
|
}
|
|
|