/* * 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 = ""; } // namespace CAddModifyContainerDlg::CAddModifyContainerDlg(const sexp_tree_view &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", static_cast(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", static_cast(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 &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", static_cast(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", static_cast(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._model.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++); }