Files
fs2open.github.com/fred2/addmodifycontainerdlg.cpp

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++);
}