diff --git a/fred2/eventeditor.cpp b/fred2/eventeditor.cpp index 1423ab058b4..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(); } @@ -1674,6 +1856,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..0f4e34f0bad 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; @@ -109,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 @@ -140,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(); @@ -150,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 00000000000..b2224d50d8c Binary files /dev/null and b/fred2/res/move_bot.bmp differ diff --git a/fred2/res/move_bot2.bmp b/fred2/res/move_bot2.bmp new file mode 100644 index 00000000000..7d6777b7e5c Binary files /dev/null and b/fred2/res/move_bot2.bmp differ diff --git a/fred2/res/move_down.bmp b/fred2/res/move_down.bmp new file mode 100644 index 00000000000..6ff650c2605 Binary files /dev/null and b/fred2/res/move_down.bmp differ diff --git a/fred2/res/move_down2.bmp b/fred2/res/move_down2.bmp new file mode 100644 index 00000000000..cadaa2d8d7b Binary files /dev/null and b/fred2/res/move_down2.bmp differ diff --git a/fred2/res/move_top.bmp b/fred2/res/move_top.bmp new file mode 100644 index 00000000000..9993a4490bd Binary files /dev/null and b/fred2/res/move_top.bmp differ diff --git a/fred2/res/move_top2.bmp b/fred2/res/move_top2.bmp new file mode 100644 index 00000000000..293f3de6931 Binary files /dev/null and b/fred2/res/move_top2.bmp differ diff --git a/fred2/res/move_up.bmp b/fred2/res/move_up.bmp new file mode 100644 index 00000000000..3dba22e08e4 Binary files /dev/null and b/fred2/res/move_up.bmp differ diff --git a/fred2/res/move_up2.bmp b/fred2/res/move_up2.bmp new file mode 100644 index 00000000000..7c8ab57b925 Binary files /dev/null and b/fred2/res/move_up2.bmp differ diff --git a/fred2/resource.h b/fred2/resource.h index 35e6ed4f750..f05d12a8885 100644 --- a/fred2/resource.h +++ b/fred2/resource.h @@ -138,6 +138,11 @@ #define IDD_VOLUMETRICS 332 #define IDD_EDIT_CUSTOM_STRINGS 333 #define IDD_SUPPORT_REARM_OPTIONS 334 +#define IDD_REORDER 335 +#define IDB_MOVE_TO_TOP 336 +#define IDB_MOVE_UP 337 +#define IDB_MOVE_DOWN 338 +#define IDB_MOVE_TO_BOTTOM 339 #define IDC_SHIP_CLASS 1003 #define IDC_SHIP_WING 1004 #define IDC_SOUND_CLIP_NAME 1007 @@ -1290,6 +1295,21 @@ #define IDC_SUPPORT_REARM_SET_ALL_UNLIMITED 1729 #define IDC_SUPPORT_REARM_SET_ALL_ZERO 1730 #define IDC_SUPPORT_REARM_POOL_TEAM 1731 +#define IDC_REORDER_TYPE 1732 +#define IDC_REORDER_LIST 1733 +#define IDC_REORDER_MOVE_TO_TOP 1734 +#define IDC_REORDER_MOVE_UP 1735 +#define IDC_REORDER_MOVE_DOWN 1736 +#define IDC_REORDER_MOVE_TO_BOTTOM 1737 +#define IDC_EVENT_MOVE_TO_TOP 1738 +#define IDC_EVENT_MOVE_UP 1739 +#define IDC_EVENT_MOVE_DOWN 1740 +#define IDC_EVENT_MOVE_TO_BOTTOM 1741 +#define IDC_MESSAGE_MOVE_TO_TOP 1742 +#define IDC_MESSAGE_MOVE_UP 1743 +#define IDC_MESSAGE_MOVE_DOWN 1744 +#define IDC_MESSAGE_MOVE_TO_BOTTOM 1745 +#define IDC_INSERT_MSG 1746 #define IDC_SEXP_POPUP_LIST 32770 #define ID_FILE_MISSIONNOTES 32771 #define ID_DUPLICATE 32774 @@ -1604,9 +1624,9 @@ #ifdef APSTUDIO_INVOKED #ifndef APSTUDIO_READONLY_SYMBOLS #define _APS_3D_CONTROLS 1 -#define _APS_NEXT_RESOURCE_VALUE 335 +#define _APS_NEXT_RESOURCE_VALUE 340 +#define _APS_NEXT_CONTROL_VALUE 1747 #define _APS_NEXT_COMMAND_VALUE 33112 -#define _APS_NEXT_CONTROL_VALUE 1732 #define _APS_NEXT_SYMED_VALUE 105 #endif #endif 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;