From 2a50cc570e74045932aaafb0b2fc597493534118 Mon Sep 17 00:00:00 2001 From: Goober5000 Date: Thu, 18 Jun 2026 23:27:11 -0400 Subject: [PATCH 1/2] Keep event annotations attached to their node across tree mutations Event annotations cache the tree item they point to as a handle (HTREEITEM in FRED, QTreeWidgetItem* in qtFRED). The handle was silently invalidated whenever the tree control deleted-and-recreated an item, so annotations got decoupled. Fix: notify when a node's handle changes. move_branch reports old->new, and free_node2 / the root-delete path report old->null (deleted). The event tree remaps the annotation's handle to follow a move, or clears it on delete (the path is left intact, so a cleared annotation is dropped at save but survives a Cancel). Co-Authored-By: Claude Opus 4.8 --- fred2/eventeditor.cpp | 15 +++++++++++++ fred2/eventeditor.h | 4 ++++ fred2/sexp_tree.cpp | 12 ++++++++++ fred2/sexp_tree.h | 5 +++++ .../dialogs/MissionEventsDialogModel.cpp | 22 +++++++++++++++++-- .../dialogs/MissionEventsDialogModel.h | 4 ++++ qtfred/src/ui/dialogs/MissionEventsDialog.cpp | 4 ++++ qtfred/src/ui/widgets/sexp_tree.cpp | 12 ++++++++++ qtfred/src/ui/widgets/sexp_tree.h | 5 +++++ 9 files changed, 81 insertions(+), 2 deletions(-) diff --git a/fred2/eventeditor.cpp b/fred2/eventeditor.cpp index 1423ab058b4..5e640dd54ad 100644 --- a/fred2/eventeditor.cpp +++ b/fred2/eventeditor.cpp @@ -1674,6 +1674,21 @@ int event_annotation_lookup(HTREEITEM handle) return -1; } +// The tree recreated (moved) or deleted the item for a node. Re-point any +// annotation on the old handle to the new one so it follows the node; a null +// new_handle means the node was deleted, which clears the handle. The path is +// left intact, so an annotation cleared this way is dropped at save (its path +// can no longer be rebuilt) but survives a Cancel (the path still resolves). +void event_sexp_tree::on_node_handle_changed(HTREEITEM old_handle, HTREEITEM new_handle) +{ + if (!old_handle) + return; + + int i = event_annotation_lookup(old_handle); + if (i >= 0) + Event_annotations[i].handle = new_handle; +} + void event_annotation_swap_image(event_sexp_tree *tree, HTREEITEM handle, int annotation_index) { event_annotation_swap_image(tree, handle, Event_annotations[annotation_index]); diff --git a/fred2/eventeditor.h b/fred2/eventeditor.h index 5cb77319730..f0ef22ccd5d 100644 --- a/fred2/eventeditor.h +++ b/fred2/eventeditor.h @@ -31,6 +31,10 @@ class event_sexp_tree : public sexp_tree virtual void PreSubclassWindow(); virtual void OnCustomDraw(NMHDR* pNMHDR, LRESULT* pResult); + // keep event annotations attached to their node when the tree recreates or + // deletes its handle (new_handle == nullptr means the node was deleted) + void on_node_handle_changed(HTREEITEM old_handle, HTREEITEM new_handle) override; + CStringA m_tooltiptextA; CStringW m_tooltiptextW; diff --git a/fred2/sexp_tree.cpp b/fred2/sexp_tree.cpp index a37a1d03cdf..8d6372cee39 100644 --- a/fred2/sexp_tree.cpp +++ b/fred2/sexp_tree.cpp @@ -479,6 +479,11 @@ void sexp_tree::free_node2(int node) Assert(tree_nodes[node].type != SEXPT_UNUSED); Assert(total_nodes > 0); *modified = 1; + + // the node is being deleted; let subclasses drop anything attached to its handle + if (tree_nodes[node].handle) + on_node_handle_changed(tree_nodes[node].handle, nullptr); + tree_nodes[node].type = SEXPT_UNUSED; total_nodes--; if (tree_nodes[node].child != -1) @@ -2640,6 +2645,9 @@ void sexp_tree::NodeDelete() Assert(theNode >= 0); free_node2(theNode); + // the event-root item is not a tree_nodes entry, so free_node2 above does + // not cover an annotation attached to the event root itself + on_node_handle_changed(item_handle, nullptr); DeleteItem(item_handle); *modified = 1; return; @@ -4668,6 +4676,10 @@ HTREEITEM sexp_tree::move_branch(HTREEITEM source, HTREEITEM parent, HTREEITEM a h = insert(GetItemText(source), image1, image2, parent, after); } + // the item was recreated with a new handle; let subclasses follow it + // (covers both tree_nodes entries and the event-root item, which is not one) + on_node_handle_changed(source, h); + SetItemData(h, GetItemData(source)); child = GetChildItem(source); while (child) { diff --git a/fred2/sexp_tree.h b/fred2/sexp_tree.h index fd712f4009d..3dffb01afc6 100644 --- a/fred2/sexp_tree.h +++ b/fred2/sexp_tree.h @@ -367,6 +367,11 @@ class sexp_tree : public CTreeCtrl virtual void NodeReplacePaste(); virtual void NodeAddPaste(); + // Notifies that the tree item for a node changed handle (moved/recreated), or + // was deleted (new_handle == nullptr). The event tree overrides this to keep + // event annotations attached to their node. Default: no-op. + virtual void on_node_handle_changed(HTREEITEM /*old_handle*/, HTREEITEM /*new_handle*/) {} + void update_item(HTREEITEM handle); int load_branch(int index, int parent); diff --git a/qtfred/src/mission/dialogs/MissionEventsDialogModel.cpp b/qtfred/src/mission/dialogs/MissionEventsDialogModel.cpp index 386b59b7dc3..7d9a4c2bb82 100644 --- a/qtfred/src/mission/dialogs/MissionEventsDialogModel.cpp +++ b/qtfred/src/mission/dialogs/MissionEventsDialogModel.cpp @@ -609,8 +609,12 @@ void MissionEventsDialogModel::reorderByRootFormulaOrder(const SCP_vector& // Keep selection reasonable (select the first event after reorder) setCurrentlySelectedEvent(m_events.empty() ? -1 : 0); - // Rebuild applied annotations against new handles/order if needed - initializeEventAnnotations(); + // A root reorder moves top-level items via take/insert, which preserves their + // handles (see sexp_tree::move_root), so the cached annotation handles are still + // valid and still point at the correct nodes. Do NOT re-resolve handles from the + // stored paths here: the paths carry the pre-reorder event index, so re-resolving + // would re-point annotations at the wrong event. applyAnnotations() rebuilds each + // path from its live handle at save time. set_modified(); } @@ -1138,6 +1142,20 @@ void MissionEventsDialogModel::setNodeBgColor(IEventTreeOps::Handle h, int r, in set_modified(); } +// The tree recreated (moved) or deleted the item for a node. Re-point any +// annotation on the old handle to the new one so it follows the node; a null +// new_handle means the node was deleted, which clears the cached handle so +// applyAnnotations() resolves it from the path (and drops it if the node is gone). +void MissionEventsDialogModel::onNodeHandleChanged(IEventTreeOps::Handle old_handle, IEventTreeOps::Handle new_handle) +{ + if (!old_handle) + return; + + for (auto& ea : m_event_annotations) + if (ea.handle == old_handle) + ea.handle = new_handle; +} + void MissionEventsDialogModel::createMessage() { MMessage msg; diff --git a/qtfred/src/mission/dialogs/MissionEventsDialogModel.h b/qtfred/src/mission/dialogs/MissionEventsDialogModel.h index 99699ad80a3..fdfb00397b2 100644 --- a/qtfred/src/mission/dialogs/MissionEventsDialogModel.h +++ b/qtfred/src/mission/dialogs/MissionEventsDialogModel.h @@ -142,6 +142,10 @@ class MissionEventsDialogModel : public AbstractDialogModel { void setNodeAnnotation(IEventTreeOps::Handle h, const SCP_string& note); void setNodeBgColor(IEventTreeOps::Handle h, int r, int g, int b, bool has_color); + // Keep annotations attached to their node when the tree recreates (move) or + // deletes (new_handle == nullptr) the item's handle. + void onNodeHandleChanged(IEventTreeOps::Handle old_handle, IEventTreeOps::Handle new_handle); + // Message Management void createMessage(); void insertMessage(); diff --git a/qtfred/src/ui/dialogs/MissionEventsDialog.cpp b/qtfred/src/ui/dialogs/MissionEventsDialog.cpp index d8a033df3a5..7c7cc94c795 100644 --- a/qtfred/src/ui/dialogs/MissionEventsDialog.cpp +++ b/qtfred/src/ui/dialogs/MissionEventsDialog.cpp @@ -245,6 +245,10 @@ void MissionEventsDialog::initEventWidgets() { _model->setNodeBgColor(h, c.red(), c.green(), c.blue(), c.isValid()); }); + connect(ui->eventTree, &sexp_tree::nodeHandleChanged, this, [this](void* old_h, void* new_h) { + _model->onNodeHandleChanged(old_h, new_h); + }); + connect(ui->eventTree, &sexp_tree::rootOrderChanged, this, [this] { SCP_vector order; order.reserve(ui->eventTree->topLevelItemCount()); diff --git a/qtfred/src/ui/widgets/sexp_tree.cpp b/qtfred/src/ui/widgets/sexp_tree.cpp index c4e0d23e7f8..6b1e6f72e79 100644 --- a/qtfred/src/ui/widgets/sexp_tree.cpp +++ b/qtfred/src/ui/widgets/sexp_tree.cpp @@ -673,6 +673,11 @@ void sexp_tree::free_node2(int node) { Assert(tree_nodes[node].type != SEXPT_UNUSED); Assert(total_nodes > 0); modified(); + + // the node is being deleted; let the model drop anything attached to its handle + if (tree_nodes[node].handle) + nodeHandleChanged(tree_nodes[node].handle, nullptr); + tree_nodes[node].type = SEXPT_UNUSED; total_nodes--; if (tree_nodes[node].child != -1) { @@ -2739,6 +2744,9 @@ QTreeWidgetItem* sexp_tree::move_branch(QTreeWidgetItem* source, QTreeWidgetItem h->setData(0, BgColorRole, source->data(0, BgColorRole)); applyVisuals(h); + // the item was recreated with a new handle; let the model follow it + nodeHandleChanged(source, h); + // Move children safely while (source->childCount() > 0) { auto* child = source->child(0); @@ -7297,6 +7305,10 @@ void sexp_tree::deleteActionHandler() free_node2(formulaNode); } + // the event-root item is not a tree_nodes entry, so free_node2 above does + // not cover an annotation attached to the event root itself + nodeHandleChanged(item, nullptr); + // Remove the UI item and reset selection/index delete item; setCurrentItemIndex(-1); diff --git a/qtfred/src/ui/widgets/sexp_tree.h b/qtfred/src/ui/widgets/sexp_tree.h index 6385b08aa16..6b939c7405f 100644 --- a/qtfred/src/ui/widgets/sexp_tree.h +++ b/qtfred/src/ui/widgets/sexp_tree.h @@ -410,6 +410,11 @@ class sexp_tree: public QTreeWidget { void nodeAnnotationChanged(void* handle, const QString& note); void nodeBgColorChanged(void* handle, const QColor& color); + // Emitted when a node's tree item changed handle (moved/recreated), or was + // deleted (new_handle == nullptr). Lets the model keep event annotations + // attached to their node across tree mutations. + void nodeHandleChanged(void* old_handle, void* new_handle); + // Generated message map functions protected: void keyPressEvent(QKeyEvent* e) override; From 91c6ab9c06f2e59f6906acd4450eb8eb40533a23 Mon Sep 17 00:00:00 2001 From: Goober5000 Date: Sat, 13 Jun 2026 01:34:36 -0400 Subject: [PATCH 2/2] Add event and message reordering to the FRED event editor Add "Move to top / up / down / to bottom" controls -- vertical icon-button strips beside both the event tree and the message list -- so events and messages can be reordered in place. Also add an "Insert Msg" button that inserts a message at the current selection, mirroring the existing "Insert Event". Tweak the message-area layout, drop the "Messages" label, and edit the AFX_DIALOG_LAYOUT table for proper behavior when the dialog is resized. Co-Authored-By: Claude Opus 4.8 --- fred2/eventeditor.cpp | 186 ++++++++++++++++++++++++++++++++++++++- fred2/eventeditor.h | 16 ++++ fred2/fred.rc | 46 +++++++--- fred2/res/move_bot.bmp | Bin 0 -> 198 bytes fred2/res/move_bot2.bmp | Bin 0 -> 202 bytes fred2/res/move_down.bmp | Bin 0 -> 194 bytes fred2/res/move_down2.bmp | Bin 0 -> 198 bytes fred2/res/move_top.bmp | Bin 0 -> 194 bytes fred2/res/move_top2.bmp | Bin 0 -> 198 bytes fred2/res/move_up.bmp | Bin 0 -> 194 bytes fred2/res/move_up2.bmp | Bin 0 -> 198 bytes fred2/resource.h | 24 ++++- 12 files changed, 257 insertions(+), 15 deletions(-) create mode 100644 fred2/res/move_bot.bmp create mode 100644 fred2/res/move_bot2.bmp create mode 100644 fred2/res/move_down.bmp create mode 100644 fred2/res/move_down2.bmp create mode 100644 fred2/res/move_top.bmp create mode 100644 fred2/res/move_top2.bmp create mode 100644 fred2/res/move_up.bmp create mode 100644 fred2/res/move_up2.bmp diff --git a/fred2/eventeditor.cpp b/fred2/eventeditor.cpp index 5e640dd54ad..63857d3c5b4 100644 --- a/fred2/eventeditor.cpp +++ b/fred2/eventeditor.cpp @@ -100,6 +100,10 @@ event_editor::event_editor(CWnd* pParent /*=NULL*/) m_log_last_trigger = 0; m_log_state_change = 0; m_play_icon = nullptr; + m_move_to_top_icon = nullptr; + m_move_up_icon = nullptr; + m_move_down_icon = nullptr; + m_move_to_bottom_icon = nullptr; } void event_editor::DoDataExchange(CDataExchange* pDX) @@ -171,6 +175,7 @@ BEGIN_MESSAGE_MAP(event_editor, CDialog) ON_BN_CLICKED(IDC_INSERT, OnInsert) ON_LBN_SELCHANGE(IDC_MESSAGE_LIST, OnSelchangeMessageList) ON_BN_CLICKED(IDC_NEW_MSG, OnNewMsg) + ON_BN_CLICKED(IDC_INSERT_MSG, OnInsertMsg) ON_BN_CLICKED(IDC_DELETE_MSG, OnDeleteMsg) ON_BN_CLICKED(IDC_NEW_NOTE, OnMsgNote) ON_BN_CLICKED(IDC_BROWSE_AVI, OnBrowseAvi) @@ -182,6 +187,14 @@ BEGIN_MESSAGE_MAP(event_editor, CDialog) ON_CBN_SELCHANGE(IDC_EVENT_TEAM, OnSelchangeTeam) ON_CBN_SELCHANGE(IDC_MESSAGE_TEAM, OnSelchangeMessageTeam) ON_LBN_DBLCLK(IDC_MESSAGE_LIST, OnDblclkMessageList) + ON_BN_CLICKED(IDC_EVENT_MOVE_TO_TOP, OnEventMoveToTop) + ON_BN_CLICKED(IDC_EVENT_MOVE_UP, OnEventMoveUp) + ON_BN_CLICKED(IDC_EVENT_MOVE_DOWN, OnEventMoveDown) + ON_BN_CLICKED(IDC_EVENT_MOVE_TO_BOTTOM, OnEventMoveToBottom) + ON_BN_CLICKED(IDC_MESSAGE_MOVE_TO_TOP, OnMessageMoveToTop) + ON_BN_CLICKED(IDC_MESSAGE_MOVE_UP, OnMessageMoveUp) + ON_BN_CLICKED(IDC_MESSAGE_MOVE_DOWN, OnMessageMoveDown) + ON_BN_CLICKED(IDC_MESSAGE_MOVE_TO_BOTTOM, OnMessageMoveToBottom) //}}AFX_MSG_MAP END_MESSAGE_MAP() @@ -207,6 +220,22 @@ BOOL event_editor::OnInitDialog() m_play_icon = load_button_icon(IDB_PLAY, RGB(192, 192, 192)); ((CButton *) GetDlgItem(IDC_PLAY)) -> SetIcon(m_play_icon); + // reorder arrows; one icon is shared between the event and message buttons. + // Icons carry a transparency mask, so they blend with the button face and + // gray out correctly when disabled (see load_button_icon). + m_move_to_top_icon = load_button_icon(IDB_MOVE_TO_TOP, RGB(255, 0, 255)); + m_move_up_icon = load_button_icon(IDB_MOVE_UP, RGB(255, 0, 255)); + m_move_down_icon = load_button_icon(IDB_MOVE_DOWN, RGB(255, 0, 255)); + m_move_to_bottom_icon = load_button_icon(IDB_MOVE_TO_BOTTOM, RGB(255, 0, 255)); + ((CButton *) GetDlgItem(IDC_EVENT_MOVE_TO_TOP)) -> SetIcon(m_move_to_top_icon); + ((CButton *) GetDlgItem(IDC_EVENT_MOVE_UP)) -> SetIcon(m_move_up_icon); + ((CButton *) GetDlgItem(IDC_EVENT_MOVE_DOWN)) -> SetIcon(m_move_down_icon); + ((CButton *) GetDlgItem(IDC_EVENT_MOVE_TO_BOTTOM)) -> SetIcon(m_move_to_bottom_icon); + ((CButton *) GetDlgItem(IDC_MESSAGE_MOVE_TO_TOP)) -> SetIcon(m_move_to_top_icon); + ((CButton *) GetDlgItem(IDC_MESSAGE_MOVE_UP)) -> SetIcon(m_move_up_icon); + ((CButton *) GetDlgItem(IDC_MESSAGE_MOVE_DOWN)) -> SetIcon(m_move_down_icon); + ((CButton *) GetDlgItem(IDC_MESSAGE_MOVE_TO_BOTTOM)) -> SetIcon(m_move_to_bottom_icon); + theApp.init_window(&Events_wnd_data, this, 0); m_event_tree.setup((CEdit *) GetDlgItem(IDC_HELP_BOX)); load_tree(); @@ -662,6 +691,7 @@ void event_editor::update_cur_message() GetDlgItem(IDC_DELETE_MSG)->EnableWindow(enable); GetDlgItem(IDC_PERSONA_NAME)->EnableWindow(enable); GetDlgItem(IDC_MESSAGE_TEAM)->EnableWindow(enable); + update_move_buttons(); UpdateData(FALSE); } @@ -1046,6 +1076,7 @@ void event_editor::update_cur_event() GetDlgItem(IDC_MISSION_LOG_LAST_TRIGGER)->EnableWindow(FALSE); GetDlgItem(IDC_MISSION_LOG_STATE_CHANGE)->EnableWindow(FALSE); + update_move_buttons(); UpdateData(FALSE); return; } @@ -1112,6 +1143,7 @@ void event_editor::update_cur_event() m_log_last_trigger = (m_events[cur_event].mission_log_flags & MLF_LAST_TRIGGER_ONLY) ? TRUE : FALSE; m_log_state_change = (m_events[cur_event].mission_log_flags & MLF_STATE_CHANGE) ? TRUE : FALSE; + update_move_buttons(); UpdateData(FALSE); } @@ -1187,7 +1219,121 @@ void event_editor::move_handler(int node1, int node2, bool insert_before) update_cur_event(); } -void event_editor::OnChained() +// Enable/disable the event and message reorder arrows based on the current +// selection and its position in the list. +void event_editor::update_move_buttons() +{ + int ecount = (int)m_events.size(); + BOOL e_up = (cur_event > 0 && cur_event < ecount) ? TRUE : FALSE; + BOOL e_down = (cur_event >= 0 && cur_event < ecount - 1) ? TRUE : FALSE; + GetDlgItem(IDC_EVENT_MOVE_TO_TOP)->EnableWindow(e_up); + GetDlgItem(IDC_EVENT_MOVE_UP)->EnableWindow(e_up); + GetDlgItem(IDC_EVENT_MOVE_DOWN)->EnableWindow(e_down); + GetDlgItem(IDC_EVENT_MOVE_TO_BOTTOM)->EnableWindow(e_down); + + int mcount = (int)m_messages.size(); + BOOL m_up = (m_cur_msg > 0 && m_cur_msg < mcount) ? TRUE : FALSE; + BOOL m_down = (m_cur_msg >= 0 && m_cur_msg < mcount - 1) ? TRUE : FALSE; + GetDlgItem(IDC_MESSAGE_MOVE_TO_TOP)->EnableWindow(m_up); + GetDlgItem(IDC_MESSAGE_MOVE_UP)->EnableWindow(m_up); + GetDlgItem(IDC_MESSAGE_MOVE_DOWN)->EnableWindow(m_down); + GetDlgItem(IDC_MESSAGE_MOVE_TO_BOTTOM)->EnableWindow(m_down); +} + +// Reorder the currently-selected event. 'up'/'all_the_way' are interpreted as: +// move up one, move to top, move down one, move to bottom. +void event_editor::move_event(bool up, bool all_the_way) +{ + int count = (int)m_events.size(); + if (cur_event < 0 || cur_event >= count) + return; + + int from = cur_event; + int to; + if (up) + to = all_the_way ? 0 : from - 1; + else + to = all_the_way ? count - 1 : from + 1; + + if (to < 0 || to >= count || to == from) + return; + + // Perform the move exactly as a drag-and-drop would: move_root shifts the + // tree item in place (the on_node_handle_changed hook keeps annotations + // attached, and the expanded state is preserved), and move_handler reorders + // m_events/m_sig to match (it also calls save() and update_cur_event()). + HTREEITEM source = get_event_handle(from); + HTREEITEM dest = get_event_handle(to); + int node1 = m_events[from].formula; + int node2 = m_events[to].formula; + bool insert_before = (to < from); + + m_event_tree.move_root(source, dest, insert_before); + move_handler(node1, node2, insert_before); + + update_move_buttons(); +} + +// Reorder the currently-selected message. 'up'/'all_the_way' are interpreted +// as: move up one, move to top, move down one, move to bottom. +void event_editor::move_message(bool up, bool all_the_way) +{ + // commit any pending edits first + save(); + + int count = (int)m_messages.size(); + if (m_cur_msg < 0 || m_cur_msg >= count) + return; + + int from = m_cur_msg; + int to; + if (up) + to = all_the_way ? 0 : from - 1; + else + to = all_the_way ? count - 1 : from + 1; + + if (to < 0 || to >= count || to == from) + return; + + // Rotate m_messages so the item at 'from' lands at 'to'. MMessage has move + // semantics, so this transfers the media-name pointers without copying. + MMessage m = std::move(m_messages[from]); + if (from < to) + { + for (int i = from; i < to; ++i) + m_messages[i] = std::move(m_messages[i + 1]); + } + else + { + for (int i = from; i > to; --i) + m_messages[i] = std::move(m_messages[i - 1]); + } + m_messages[to] = std::move(m); + + // Rebuild the listbox to match the new order. + CListBox *list = (CListBox *) GetDlgItem(IDC_MESSAGE_LIST); + list->ResetContent(); + for (const auto &msg : m_messages) + list->AddString(msg.name); + + m_cur_msg = to; + list->SetCurSel(to); + + update_cur_message(); + update_move_buttons(); + modified = 1; +} + +void event_editor::OnEventMoveToTop() { move_event(true, true); } +void event_editor::OnEventMoveUp() { move_event(true, false); } +void event_editor::OnEventMoveDown() { move_event(false, false); } +void event_editor::OnEventMoveToBottom() { move_event(false, true); } +void event_editor::OnMessageMoveToTop() { move_message(true, true); } +void event_editor::OnMessageMoveUp() { move_message(true, false); } +void event_editor::OnMessageMoveDown() { move_message(false, false); } +void event_editor::OnMessageMoveToBottom() { move_message(false, true); } + +void event_editor::OnChained() { int image; HTREEITEM h; @@ -1332,7 +1478,39 @@ void event_editor::OnNewMsg() update_cur_message(); } -void event_editor::OnDeleteMsg() +void event_editor::OnInsertMsg() +{ + // with no current message there's nothing to insert ahead of, so just append + if (m_cur_msg < 0 || m_messages.empty()) { + OnNewMsg(); + return; + } + + save(); + + MMessage msg; + strcpy_s(msg.name, ""); + strcpy_s(msg.message, ""); + msg.avi_info.name = nullptr; + msg.wave_info.name = nullptr; + msg.persona_index = -1; + msg.multi_team = -1; + + // insert ahead of the current message; the new one takes its slot + m_messages.insert(m_messages.begin() + m_cur_msg, std::move(msg)); + + // rebuild the listbox to match the new order + CListBox *list = (CListBox *) GetDlgItem(IDC_MESSAGE_LIST); + list->ResetContent(); + for (const auto &m : m_messages) + list->AddString(m.name); + list->SetCurSel(m_cur_msg); + + modified = 1; + update_cur_message(); +} + +void event_editor::OnDeleteMsg() { char buf[256]; @@ -1524,6 +1702,10 @@ BOOL event_editor::DestroyWindow() m_wave_id = -1; if (m_play_icon) DestroyIcon(m_play_icon); + if (m_move_to_top_icon) DestroyIcon(m_move_to_top_icon); + if (m_move_up_icon) DestroyIcon(m_move_up_icon); + if (m_move_down_icon) DestroyIcon(m_move_down_icon); + if (m_move_to_bottom_icon) DestroyIcon(m_move_to_bottom_icon); return CDialog::DestroyWindow(); } diff --git a/fred2/eventeditor.h b/fred2/eventeditor.h index f0ef22ccd5d..0f4e34f0bad 100644 --- a/fred2/eventeditor.h +++ b/fred2/eventeditor.h @@ -113,6 +113,10 @@ class event_editor : public CDialog //}}AFX_DATA HICON m_play_icon; + HICON m_move_to_top_icon; + HICON m_move_up_icon; + HICON m_move_down_icon; + HICON m_move_to_bottom_icon; // Overrides // ClassWizard generated virtual function overrides @@ -144,6 +148,7 @@ class event_editor : public CDialog afx_msg void OnInsert(); afx_msg void OnSelchangeMessageList(); afx_msg void OnNewMsg(); + afx_msg void OnInsertMsg(); afx_msg void OnDeleteMsg(); afx_msg void OnMsgNote(); afx_msg void OnBrowseAvi(); @@ -154,12 +159,23 @@ class event_editor : public CDialog afx_msg void OnSelchangeTeam(); afx_msg void OnSelchangeMessageTeam(); afx_msg void OnDblclkMessageList(); + afx_msg void OnEventMoveToTop(); + afx_msg void OnEventMoveUp(); + afx_msg void OnEventMoveDown(); + afx_msg void OnEventMoveToBottom(); + afx_msg void OnMessageMoveToTop(); + afx_msg void OnMessageMoveUp(); + afx_msg void OnMessageMoveDown(); + afx_msg void OnMessageMoveToBottom(); //}}AFX_MSG DECLARE_MESSAGE_MAP() private: int cur_event; void update_cur_event(); + void move_event(bool up, bool all_the_way); + void move_message(bool up, bool all_the_way); + void update_move_buttons(); SCP_vector m_sig; SCP_vector m_events; SCP_vector m_messages; diff --git a/fred2/fred.rc b/fred2/fred.rc index a668814effe..8f2f009cd6f 100644 --- a/fred2/fred.rc +++ b/fred2/fred.rc @@ -158,6 +158,14 @@ IDB_CONTAINER_NAME BITMAP "res\\container_name.bmp" IDB_CONTAINER_DATA BITMAP "res\\container_data.bmp" +IDB_MOVE_TO_TOP BITMAP "res\\move_top.bmp" + +IDB_MOVE_UP BITMAP "res\\move_up.bmp" + +IDB_MOVE_DOWN BITMAP "res\\move_down.bmp" + +IDB_MOVE_TO_BOTTOM BITMAP "res\\move_bot.bmp" + ///////////////////////////////////////////////////////////////////////////// // @@ -1455,9 +1463,9 @@ CAPTION "Mission Event Edit" FONT 8, "MS Sans Serif", 0, 0, 0x1 BEGIN CONTROL "Tree1",IDC_EVENT_TREE,"SysTreeView32",TVS_HASBUTTONS | TVS_HASLINES | TVS_LINESATROOT | TVS_EDITLABELS | TVS_SHOWSELALWAYS | WS_BORDER | WS_HSCROLL | WS_TABSTOP,7,7,196,343,WS_EX_CLIENTEDGE - PUSHBUTTON "New Event",IDC_BUTTON_NEW_EVENT,207,7,50,14,0,WS_EX_STATICEDGE,HIDC_BUTTON_NEW_EVENT - PUSHBUTTON "Insert Event",IDC_INSERT,207,24,50,14,0,WS_EX_STATICEDGE - PUSHBUTTON "Delete Event",IDC_DELETE,207,41,50,14,0,WS_EX_STATICEDGE + PUSHBUTTON "New Event",IDC_BUTTON_NEW_EVENT,227,7,48,14,0,WS_EX_STATICEDGE,HIDC_BUTTON_NEW_EVENT + PUSHBUTTON "Insert Event",IDC_INSERT,227,23,48,14,0,WS_EX_STATICEDGE + PUSHBUTTON "Delete Event",IDC_DELETE,227,39,48,14,0,WS_EX_STATICEDGE EDITTEXT IDC_REPEAT_COUNT,208,108,45,14,ES_AUTOHSCROLL EDITTEXT IDC_INTERVAL_TIME,208,165,45,14,ES_AUTOHSCROLL | ES_NUMBER EDITTEXT IDC_EVENT_SCORE,208,239,45,14,ES_AUTOHSCROLL @@ -1468,9 +1476,10 @@ BEGIN EDITTEXT IDC_OBJ_TEXT,86,354,117,14,ES_AUTOHSCROLL EDITTEXT IDC_OBJ_KEY_TEXT,86,370,117,14,ES_AUTOHSCROLL EDITTEXT IDC_HELP_BOX,208,313,222,111,ES_MULTILINE | ES_AUTOVSCROLL | ES_READONLY | WS_VSCROLL,WS_EX_TRANSPARENT - PUSHBUTTON "New Msg",IDC_NEW_MSG,229,59,50,14,0,WS_EX_STATICEDGE - PUSHBUTTON "Delete Msg",IDC_DELETE_MSG,229,77,50,14,0,WS_EX_STATICEDGE - LISTBOX IDC_MESSAGE_LIST,285,17,145,74,LBS_NOINTEGRALHEIGHT | WS_VSCROLL | WS_TABSTOP + PUSHBUTTON "New Msg",IDC_NEW_MSG,387,7,43,14,0,WS_EX_STATICEDGE + PUSHBUTTON "Insert Msg",IDC_INSERT_MSG,387,23,43,14,0,WS_EX_STATICEDGE + PUSHBUTTON "Delete Msg",IDC_DELETE_MSG,387,39,43,14,0,WS_EX_STATICEDGE + LISTBOX IDC_MESSAGE_LIST,285,7,78,84,LBS_NOINTEGRALHEIGHT | WS_VSCROLL | WS_TABSTOP EDITTEXT IDC_MESSAGE_NAME,285,95,145,14,ES_AUTOHSCROLL EDITTEXT IDC_MESSAGE_TEXT,261,125,169,54,ES_MULTILINE | ES_AUTOVSCROLL | ES_WANTRETURN COMBOBOX IDC_AVI_FILENAME,303,194,67,111,CBS_DROPDOWN | CBS_AUTOHSCROLL | CBS_SORT | WS_VSCROLL | WS_TABSTOP @@ -1490,7 +1499,6 @@ BEGIN LTEXT "Directive keypress text",IDC_STATIC,7,372,74,8 LTEXT "Message Text",IDC_STATIC,261,114,48,8 LTEXT "Name",IDC_STATIC,261,97,20,8 - LTEXT "Messages",IDC_STATIC,335,7,35,8 LTEXT "ANI file",IDC_STATIC,275,197,23,8 LTEXT "Wave file",IDC_STATIC,267,212,31,8 LTEXT "Persona",IDC_STATIC,271,227,27,8 @@ -1511,6 +1519,14 @@ BEGIN LTEXT "Log These States To The Event.log",IDC_STATIC,37,386,114,8 CONTROL "Interval && Chain in Milliseconds",IDC_USE_MSECS,"Button",BS_AUTOCHECKBOX | BS_MULTILINE | WS_TABSTOP,208,275,65,19 PUSHBUTTON "Add Note",IDC_NEW_NOTE,380,111,50,12,0,WS_EX_STATICEDGE + PUSHBUTTON "Top",IDC_EVENT_MOVE_TO_TOP,207,7,16,16,BS_ICON,WS_EX_STATICEDGE + PUSHBUTTON "Up",IDC_EVENT_MOVE_UP,207,23,16,16,BS_ICON,WS_EX_STATICEDGE + PUSHBUTTON "Down",IDC_EVENT_MOVE_DOWN,207,39,16,16,BS_ICON,WS_EX_STATICEDGE + PUSHBUTTON "Bottom",IDC_EVENT_MOVE_TO_BOTTOM,207,55,16,16,BS_ICON,WS_EX_STATICEDGE + PUSHBUTTON "Top",IDC_MESSAGE_MOVE_TO_TOP,367,7,16,16,BS_ICON,WS_EX_STATICEDGE + PUSHBUTTON "Up",IDC_MESSAGE_MOVE_UP,367,23,16,16,BS_ICON,WS_EX_STATICEDGE + PUSHBUTTON "Down",IDC_MESSAGE_MOVE_DOWN,367,39,16,16,BS_ICON,WS_EX_STATICEDGE + PUSHBUTTON "Bottom",IDC_MESSAGE_MOVE_TO_BOTTOM,367,55,16,16,BS_ICON,WS_EX_STATICEDGE END IDD_BG_BITMAP DIALOGEX 0, 0, 431, 470 @@ -3412,8 +3428,9 @@ BEGIN 0, 100, 50, 0, 0, 100, 50, 0, 50, 50, 50, 50, - 50, 50, 0, 0, - 50, 50, 0, 0, + 100, 0, 0, 0, + 100, 0, 0, 0, + 100, 0, 0, 0, 50, 0, 50, 50, 50, 50, 50, 0, 50, 50, 50, 0, @@ -3434,7 +3451,6 @@ BEGIN 0, 100, 0, 0, 50, 50, 0, 0, 50, 50, 0, 0, - 75, 0, 0, 0, 50, 50, 0, 0, 50, 50, 0, 0, 50, 50, 0, 0, @@ -3454,7 +3470,15 @@ BEGIN 25, 100, 0, 0, 25, 100, 0, 0, 50, 50, 0, 0, - 50, 50, 0, 0 + 100, 50, 0, 0, + 50, 0, 0, 0, + 50, 0, 0, 0, + 50, 0, 0, 0, + 50, 0, 0, 0, + 100, 0, 0, 0, + 100, 0, 0, 0, + 100, 0, 0, 0, + 100, 0, 0, 0 END IDD_ASTEROID_EDITOR AFX_DIALOG_LAYOUT diff --git a/fred2/res/move_bot.bmp b/fred2/res/move_bot.bmp new file mode 100644 index 0000000000000000000000000000000000000000..b2224d50d8cb4a73ddd13e84a42ef17c85e9c04a GIT binary patch literal 198 zcmZ`zISzm@3`;6z)bs`I#>y8mu<@<#{S)F_AqRBBlH%H51^fjerCOlb)W*|f}AYqDvu}ZLEEkVHe;fL9-{Q$$(4B7wy literal 0 HcmV?d00001 diff --git a/fred2/res/move_down2.bmp b/fred2/res/move_down2.bmp new file mode 100644 index 0000000000000000000000000000000000000000..cadaa2d8d7bfd55b35139fae55aa2bee6ce0d1e0 GIT binary patch literal 198 zcmX|*F%HBa3L@lZuxZkUCs3rjZ}G8{@+4Y^h3HmVk66Qs`n_rC8aRgc*G!+a z81uI+BY}e=dhbzdjn-QHFA_1{4~a~0I%gmyBjDtYfQx$uy<`WoS^-bB1d5t~yBZpq M)aK8?skeU}9@IV*C;$Ke literal 0 HcmV?d00001 diff --git a/fred2/res/move_top.bmp b/fred2/res/move_top.bmp new file mode 100644 index 0000000000000000000000000000000000000000..9993a4490bd25256cc1f797efae6f69abf88a945 GIT binary patch literal 194 zcmZ{b!4ZHk2m~Sec0wu$KkMLcTPe)3FFb{5vAz_=SGhxt3Trt{N0=5>$~EEFwKfI!6RK&J0DM=wD`j1Iw-rWB>pF literal 0 HcmV?d00001 diff --git a/fred2/res/move_top2.bmp b/fred2/res/move_top2.bmp new file mode 100644 index 0000000000000000000000000000000000000000..293f3de69315b4ebcd63da4bc728cf616852606f GIT binary patch literal 198 zcmXYpF%APU2n3Ck`gGtIfSa`EC%CvS-}1|%dy-qi;;ghgVnK}0Q?~BFJAD34<+T@) z-(wpM?8SYpQ36?%!ArD)O|*m7n}OY|fz|8^7Va${Qq7S# Ml=o(NA4u;02M%Ku(*OVf literal 0 HcmV?d00001 diff --git a/fred2/res/move_up.bmp b/fred2/res/move_up.bmp new file mode 100644 index 0000000000000000000000000000000000000000..3dba22e08e407d1781feeeac3c12d3e154f0ee54 GIT binary patch literal 194 zcmZ{eK@Na02n1X7Y#LtB?#Xv>_gj5feHcP9dN7hKvn(8tW27iC?51)nr!^MV&n>i= qcWF}Xy{m}mbMzXj;eS}DW$QBbLXPBB(T__i_)Nxb6DLQEbe