/* ** Command & Conquer Renegade(tm) ** Copyright 2025 Electronic Arts Inc. ** ** This program is free software: you can redistribute it and/or modify ** it under the terms of the GNU General Public License as published by ** the Free Software Foundation, either version 3 of the License, or ** (at your option) any later version. ** ** This program is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ** GNU General Public License for more details. ** ** You should have received a copy of the GNU General Public License ** along with this program. If not, see . */ /*********************************************************************************************** *** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S *** *********************************************************************************************** * * * Project Name : LevelEdit * * * * $Archive:: /Commando/Code/Tools/LevelEdit/ConversationPage.cpp $* * * * Author:: Patrick Smith * * * * $Modtime:: 2/12/02 9:57a $* * * * $Revision:: 5 $* * * *---------------------------------------------------------------------------------------------* * Functions: * * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ #include "stdafx.h" #include "leveledit.h" #include "conversationpage.h" #include "nodemgr.h" #include "node.h" #include "icons.h" #include "conversation.h" #include "conversationmgr.h" #include "editconversationdialog.h" #include "conversationeditormgr.h" #ifdef _DEBUG #define new DEBUG_NEW #undef THIS_FILE static char THIS_FILE[] = __FILE__; #endif /////////////////////////////////////////////////////////////////////// // // Helper objects // /////////////////////////////////////////////////////////////////////// class TreeStateCaptureClass { public: TreeStateCaptureClass (CTreeCtrl &tree_ctrl, HTREEITEM *item_handle, ConversationClass **conversation_ptr) : ConversationID (0), ConversationPtr (conversation_ptr), ItemHandle (item_handle), TreeCtrl (tree_ctrl) { if (conversation_ptr != NULL) { ConversationID = (*conversation_ptr)->Get_ID (); } return ; } void Restore (void) { if (ItemHandle != NULL) { (*ItemHandle) = NULL; } if (ConversationPtr != NULL) { (*ConversationPtr) = NULL; } Find_Entry (TVI_ROOT); // // Ensure the item is visible // if (ItemHandle != NULL && (*ItemHandle) != NULL) { TreeCtrl.SelectItem (*ItemHandle); TreeCtrl.EnsureVisible (*ItemHandle); } return ; } bool Find_Entry (HTREEITEM parent_item) { bool retval = false; // // Loop over all the children of this tree item // for ( HTREEITEM child_item = TreeCtrl.GetNextItem (parent_item, TVGN_CHILD); retval == false && child_item != NULL; child_item = TreeCtrl.GetNextItem (child_item, TVGN_NEXT)) { // // Is this the conversation we are looking for? // ConversationClass *conversation = (ConversationClass *)TreeCtrl.GetItemData (child_item); if (conversation != NULL && conversation->Get_ID () == ConversationID) { if (ItemHandle != NULL) { (*ItemHandle) = child_item; } if (ConversationPtr != NULL) { (*ConversationPtr) = conversation; } retval = true; break ; } // // Recurse into this child (if necessary) // if (TreeCtrl.ItemHasChildren (child_item)) { retval = Find_Entry (child_item); } } return retval; } private: int ConversationID; CTreeCtrl & TreeCtrl; HTREEITEM * ItemHandle; ConversationClass ** ConversationPtr; }; ///////////////////////////////////////////////////////////////////////////// // Static member initialization ///////////////////////////////////////////////////////////////////////////// ConversationPageClass * ConversationPageClass::_TheInstance = NULL; ///////////////////////////////////////////////////////////////////////////// // Local constants ///////////////////////////////////////////////////////////////////////////// static const int TOOLBAR_HEIGHT = 36; static const int TOOLBAR_V_SPACING = 5; static const int TOOLBAR_V_BORDER = TOOLBAR_V_SPACING * 2; static const int TOOLBAR_H_SPACING = 5; static const int TOOLBAR_H_BORDER = TOOLBAR_H_SPACING * 2; ///////////////////////////////////////////////////////////////////////////// // // ConversationPageClass // ///////////////////////////////////////////////////////////////////////////// ConversationPageClass::ConversationPageClass(CWnd* pParent /*=NULL*/) : m_GlobalsRoot (NULL), m_LevelsRoot (NULL), CDialog(ConversationPageClass::IDD, pParent) { //{{AFX_DATA_INIT(ConversationPageClass) // NOTE: the ClassWizard will add member initialization here //}}AFX_DATA_INIT Create (ConversationPageClass::IDD, pParent); _TheInstance = this; return ; } ///////////////////////////////////////////////////////////////////////////// // // ~ConversationPageClass // ///////////////////////////////////////////////////////////////////////////// ConversationPageClass::~ConversationPageClass (void) { _TheInstance = NULL; return ; } ///////////////////////////////////////////////////////////////////////////// // // DoDataExchange // ///////////////////////////////////////////////////////////////////////////// void ConversationPageClass::DoDataExchange (CDataExchange *pDX) { CDialog::DoDataExchange(pDX); //{{AFX_DATA_MAP(ConversationPageClass) DDX_Control(pDX, IDC_CONVERSATION_TREE, m_TreeCtrl); //}}AFX_DATA_MAP return ; } BEGIN_MESSAGE_MAP(ConversationPageClass, CDialog) //{{AFX_MSG_MAP(ConversationPageClass) ON_NOTIFY(TVN_DELETEITEM, IDC_CONVERSATION_TREE, OnDeleteItemConversationTree) ON_WM_SIZE() ON_WM_DESTROY() ON_NOTIFY(NM_DBLCLK, IDC_CONVERSATION_TREE, OnDblclkConversationTree) ON_NOTIFY(TVN_ITEMEXPANDED, IDC_CONVERSATION_TREE, OnItemexpandedConversationTree) ON_COMMAND(IDC_ADD, OnAdd) ON_COMMAND(IDC_DELETE, OnDelete) ON_COMMAND(IDC_EDIT, OnEdit) ON_COMMAND(IDC_SWAP, OnSwap) //}}AFX_MSG_MAP END_MESSAGE_MAP() ///////////////////////////////////////////////////////////////////////////// // // OnSize // ///////////////////////////////////////////////////////////////////////////// void ConversationPageClass::OnSize ( UINT nType, int cx, int cy ) { // Allow the base class to process this message CDialog::OnSize (nType, cx, cy); if (::IsWindow (m_TreeCtrl) && (cx > 0) && (cy > 0)) { // Get the bounding rectangle of the form window CRect parentrect; GetWindowRect (&parentrect); // Get the bounding rectangle of the toolbar CRect toolbar_rect; m_Toolbar.GetWindowRect (&toolbar_rect); ScreenToClient (&toolbar_rect); // Move the toolbar so it is in its correct position m_Toolbar.SetWindowPos (NULL, TOOLBAR_H_SPACING, (cy - TOOLBAR_V_SPACING) - toolbar_rect.Height (), cx - TOOLBAR_H_BORDER, toolbar_rect.Height (), SWP_NOZORDER); // Get the bounding rectnagle of the list ctrl RECT list_rect; m_TreeCtrl.GetWindowRect (&list_rect); CRect client_rect = list_rect; ScreenToClient (&client_rect); int list_height = ((cy - TOOLBAR_V_BORDER) - toolbar_rect.Height ()) - client_rect.top; // Resize the tab control to fill the entire contents of the client area m_TreeCtrl.SetWindowPos ( NULL, 0, 0, cx-((list_rect.left - parentrect.left) << 1), list_height, SWP_NOZORDER | SWP_NOMOVE); } return ; } ///////////////////////////////////////////////////////////////////////////// // // OnDestroy // ///////////////////////////////////////////////////////////////////////////// void ConversationPageClass::OnDestroy (void) { // Free the state image list we associated with the control CImageList *imagelist = m_TreeCtrl.GetImageList (TVSIL_STATE); m_TreeCtrl.SetImageList (NULL, TVSIL_STATE); SAFE_DELETE (imagelist); // Remove the main image list we associated with the control m_TreeCtrl.SetImageList (NULL, TVSIL_NORMAL); m_TreeCtrl.DeleteAllItems (); ::RemoveProp (m_TreeCtrl, "TRANS_ACCS"); ::RemoveProp (m_hWnd, "TRANS_ACCS"); // Allow the base class to process this message CDialog::OnDestroy (); return ; } //////////////////////////////////////////////////////////////////////////// // // OnInitDialog // //////////////////////////////////////////////////////////////////////////// BOOL ConversationPageClass::OnInitDialog (void) { CDialog::OnInitDialog (); m_Toolbar.CreateEx (this, TBSTYLE_FLAT, WS_CHILD | WS_VISIBLE | CBRS_ALIGN_TOP, CRect(0, 0, 0, 0), 101); m_Toolbar.SetOwner (this); m_Toolbar.LoadToolBar (IDR_CONVERSATION_TOOLBAR); m_Toolbar.SetBarStyle (m_Toolbar.GetBarStyle () | CBRS_TOOLTIPS | CBRS_FLYBY); // // Size the toolbar // CRect parentrect; GetWindowRect (&parentrect); m_Toolbar.SetWindowPos (NULL, 0, 0, parentrect.Width () - TOOLBAR_H_BORDER, TOOLBAR_HEIGHT, SWP_NOZORDER | SWP_NOMOVE); // // Pass the general use imagelist onto the tree control // m_TreeCtrl.SetImageList (::Get_Global_Image_List (), TVSIL_NORMAL); SetProp (m_TreeCtrl, "TRANS_ACCS", (HANDLE)1); SetProp (m_hWnd, "TRANS_ACCS", (HANDLE)1); Reload_Data (); return TRUE; } ///////////////////////////////////////////////////////////////////////////// // // OnDeleteItemConversationTree // ///////////////////////////////////////////////////////////////////////////// void ConversationPageClass::OnDeleteItemConversationTree ( NMHDR * pNMHDR, LRESULT *pResult ) { NM_TREEVIEW* pNMTreeView = (NM_TREEVIEW*)pNMHDR; (*pResult) = 0; // // Release our hold on the conversation object // ConversationClass *conversation = (ConversationClass *)pNMTreeView->itemOld.lParam; REF_PTR_RELEASE (conversation); m_TreeCtrl.SetItemData (pNMTreeView->itemOld.hItem, 0); return ; } ///////////////////////////////////////////////////////////////////////////// // // Insert_Entry // ///////////////////////////////////////////////////////////////////////////// void ConversationPageClass::Insert_Entry (ConversationClass *conversation, bool sort_items) { // // Determine which folder this conversation goes under // HTREEITEM parent_item = m_LevelsRoot; if (conversation->Get_Category_ID () != ConversationMgrClass::CATEGORY_LEVEL) { parent_item = m_GlobalsRoot; } // // Insert this item into the tree // HTREEITEM tree_item = m_TreeCtrl.InsertItem (conversation->Get_Name (), DIALOGUE_ICON, DIALOGUE_ICON, parent_item); if (tree_item != NULL) { // // Lock a reference on the object // conversation->Add_Ref (); // // Associate the conversation with the entry in the tree // m_TreeCtrl.SetItemData (tree_item, (DWORD)conversation); // // Sort the items // if (sort_items) { m_TreeCtrl.InvalidateRect (NULL, TRUE); m_TreeCtrl.SortChildren (parent_item); } } return ; } ///////////////////////////////////////////////////////////////////////////// // // Reload_Data // ///////////////////////////////////////////////////////////////////////////// void ConversationPageClass::Reload_Data (void) { Reset_Tree (); // // Add all the conversations to the tree // int count = ConversationMgrClass::Get_Conversation_Count (); for (int index = 0; index < count; index ++) { Insert_Entry (ConversationMgrClass::Peek_Conversation (index), false); } m_TreeCtrl.InvalidateRect (NULL, TRUE); m_TreeCtrl.SortChildren (m_GlobalsRoot); m_TreeCtrl.SortChildren (m_LevelsRoot); return ; } ///////////////////////////////////////////////////////////////////////////// // // Reset_Tree // ///////////////////////////////////////////////////////////////////////////// void ConversationPageClass::Reset_Tree (void) { m_TreeCtrl.DeleteAllItems (); // // Insert the root nodes // m_GlobalsRoot = m_TreeCtrl.InsertItem ("Globals", FOLDER_ICON, FOLDER_ICON); m_LevelsRoot = m_TreeCtrl.InsertItem ("Level Specific", FOLDER_ICON, FOLDER_ICON); return ; } //////////////////////////////////////////////////////////////////////////// // // OnDblclkConversationTree // //////////////////////////////////////////////////////////////////////////// void ConversationPageClass::OnDblclkConversationTree ( NMHDR * pNMHDR, LRESULT * pResult ) { // // Determine what client-coord location was double-clicked on // DWORD mouse_pos = ::GetMessagePos (); POINT hit_point = { GET_X_LPARAM (mouse_pos), GET_Y_LPARAM (mouse_pos) }; m_TreeCtrl.ScreenToClient (&hit_point); // // Goto the node that was double-clicked on (if possible) // UINT flags = 0; HTREEITEM tree_item = m_TreeCtrl.HitTest (hit_point, &flags); if (tree_item != NULL && flags & TVHT_ONITEMLABEL) { // // Edit the entry the user double-clicked on // Edit_Entry (tree_item); (*pResult) = 1; } else { (*pResult) = 0; } return ; } //////////////////////////////////////////////////////////////////////////// // // OnItemexpandedConversationTree // //////////////////////////////////////////////////////////////////////////// void ConversationPageClass::OnItemexpandedConversationTree ( NMHDR * pNMHDR, LRESULT * pResult ) { NM_TREEVIEW* pNMTreeView = (NM_TREEVIEW*)pNMHDR; // // If this is a folder, then change its image based // on its expanded state. // if (m_TreeCtrl.GetItemData (pNMTreeView->itemNew.hItem) == NULL) { if (pNMTreeView->itemNew.state & TVIS_EXPANDED) { m_TreeCtrl.SetItemImage (pNMTreeView->itemNew.hItem, OPEN_FOLDER_ICON, OPEN_FOLDER_ICON); } else { m_TreeCtrl.SetItemImage (pNMTreeView->itemNew.hItem, FOLDER_ICON, FOLDER_ICON); } } (*pResult) = 0; return ; } //////////////////////////////////////////////////////////////////////////// // // OnAdd // //////////////////////////////////////////////////////////////////////////// void ConversationPageClass::OnAdd (void) { HTREEITEM selected_item = m_TreeCtrl.GetSelectedItem (); if (selected_item != NULL) { // // Determine if this is a global conversation // bool is_global = false; bool can_edit = true; bool check_in = false; if ( selected_item == m_GlobalsRoot || m_TreeCtrl.GetParentItem (selected_item) == m_GlobalsRoot) { is_global = true; // // Check out the database // CWaitCursor wait_cursor; check_in = ConversationEditorMgrClass::Check_Out (); can_edit = check_in; // // Reload the tree // if (can_edit) { Reload_Tree (&selected_item, NULL); } } if (can_edit) { // // Show a dialog to the user that will allow them to edit this conversation // EditConversationDialogClass dialog; if (dialog.DoModal () == IDOK) { ConversationClass *conversation = dialog.Peek_Conversation (); // // Determine which folder to put this conversation under // if (is_global) { conversation->Set_Category_ID (ConversationMgrClass::CATEGORY_GLOBAL); } else { conversation->Set_Category_ID (ConversationMgrClass::CATEGORY_LEVEL); ::Set_Modified (); } // // Add the conversation to the manager // ConversationMgrClass::Add_Conversation (conversation); // // Add the conversation to the tree // Insert_Entry (conversation); m_TreeCtrl.UpdateWindow (); // // Check in the global conversation database // if (check_in) { CWaitCursor wait_cursor; ConversationEditorMgrClass::Save_Global_Database (); ConversationEditorMgrClass::Check_In (); } } else if (check_in) { CWaitCursor wait_cursor; ConversationEditorMgrClass::Undo_Check_Out (); } } } return ; } //////////////////////////////////////////////////////////////////////////// // // OnDelete // //////////////////////////////////////////////////////////////////////////// void ConversationPageClass::OnDelete (void) { HTREEITEM selected_item = m_TreeCtrl.GetSelectedItem (); if (selected_item != NULL) { // // Get the conversation associated with the selected item // ConversationClass *conversation = NULL; conversation = (ConversationClass *)m_TreeCtrl.GetItemData (selected_item); if (conversation != NULL) { // // This "dirties" the level, so mark it as modified... // if (conversation->Get_Category_ID () == ConversationMgrClass::CATEGORY_LEVEL) { ::Set_Modified (); } // // Check out the global conversation database (if necessary) // bool check_in = false; bool can_edit = true; if (conversation->Get_Category_ID () == ConversationMgrClass::CATEGORY_GLOBAL) { CWaitCursor wait_cursor; check_in = ConversationEditorMgrClass::Check_Out (); can_edit = check_in; // // Reload the tree // if (can_edit) { Reload_Tree (&selected_item, &conversation); } } if (can_edit && selected_item != NULL && conversation != NULL) { // // Remove this conversation from the manager // ConversationMgrClass::Remove_Conversation (conversation); // // Remove this conversation from the tree // m_TreeCtrl.DeleteItem (selected_item); m_TreeCtrl.UpdateWindow (); // // Check in the global conversation database // if (check_in) { ConversationEditorMgrClass::Save_Global_Database (); ConversationEditorMgrClass::Check_In (); } } } } return ; } //////////////////////////////////////////////////////////////////////////// // // OnEdit // //////////////////////////////////////////////////////////////////////////// void ConversationPageClass::OnEdit (void) { HTREEITEM selected_item = m_TreeCtrl.GetSelectedItem (); if (selected_item != NULL) { // // Edit the selected entry // Edit_Entry (selected_item); } return ; } //////////////////////////////////////////////////////////////////////////// // // Edit_Entry // //////////////////////////////////////////////////////////////////////////// void ConversationPageClass::Edit_Entry (HTREEITEM tree_item) { if (tree_item != NULL) { // // Edit the conversation (if necessary) // ConversationClass *conversation = (ConversationClass *)m_TreeCtrl.GetItemData (tree_item); if (conversation != NULL) { // // Check out the global conversation database (if necessary) // bool check_in = false; bool can_edit = true; if (conversation->Get_Category_ID () == ConversationMgrClass::CATEGORY_GLOBAL) { CWaitCursor wait_cursor; check_in = ConversationEditorMgrClass::Check_Out (); can_edit = check_in; // // Reload the tree // if (can_edit) { Reload_Tree (&tree_item, &conversation); } } if (can_edit && tree_item != NULL && conversation != NULL) { // // Display a dialog to the user allowing them to edit the conversation // EditConversationDialogClass dialog; dialog.Set_Conversation (conversation); if (dialog.DoModal () == IDOK) { m_TreeCtrl.SetItemText (tree_item, conversation->Get_Name ()); m_TreeCtrl.UpdateWindow (); // // This "dirties" the level, so mark it as modified... // if (conversation->Get_Category_ID () == ConversationMgrClass::CATEGORY_LEVEL) { ::Set_Modified (); } // // Check in the global conversation database // if (check_in) { CWaitCursor wait_cursor; ConversationEditorMgrClass::Save_Global_Database (); ConversationEditorMgrClass::Check_In (); } } else if (check_in) { CWaitCursor wait_cursor; ConversationEditorMgrClass::Undo_Check_Out (); } } } } return ; } //////////////////////////////////////////////////////////////////////////// // // OnSwap // //////////////////////////////////////////////////////////////////////////// void ConversationPageClass::OnSwap (void) { HTREEITEM selected_item = m_TreeCtrl.GetSelectedItem (); if (selected_item != NULL) { // // Get the conversation associated with the selected item // ConversationClass *conversation = NULL; conversation = (ConversationClass *)m_TreeCtrl.GetItemData (selected_item); if (conversation != NULL) { // // Check out the global conversation database // CWaitCursor wait_cursor; if (ConversationEditorMgrClass::Check_Out ()) { // // Reload the tree // Reload_Tree (&selected_item, &conversation); if (selected_item != NULL && conversation != NULL) { // // Remove the conversation from the system // ConversationMgrClass::Remove_Conversation (conversation); // // Swap categories // int category_id = conversation->Get_Category_ID (); if (category_id == ConversationMgrClass::CATEGORY_LEVEL) { conversation->Set_Category_ID (ConversationMgrClass::CATEGORY_GLOBAL); } else { conversation->Set_Category_ID (ConversationMgrClass::CATEGORY_LEVEL); } // // Re-add the conversation to the manager and the UI // ConversationMgrClass::Add_Conversation (conversation); Insert_Entry (conversation); // // Remove the old entry // m_TreeCtrl.DeleteItem (selected_item); m_TreeCtrl.UpdateWindow (); // // This "dirties" the level, so mark it as modified... // ::Set_Modified (); // // Checkin the global conversation database // ConversationEditorMgrClass::Save_Global_Database (); ConversationEditorMgrClass::Check_In (); } } } } return ; } //////////////////////////////////////////////////////////////////////////// // // Reload_Tree // //////////////////////////////////////////////////////////////////////////// void ConversationPageClass::Reload_Tree (HTREEITEM *item_handle, ConversationClass **conversation_ptr) { m_TreeCtrl.SetRedraw (FALSE); // // Reload the database while capturing its state // TreeStateCaptureClass capture_obj (m_TreeCtrl, item_handle, conversation_ptr); ConversationEditorMgrClass::Load_Global_Database (); capture_obj.Restore (); // // Expand the two root folders by default // m_TreeCtrl.Expand (m_GlobalsRoot, TVE_EXPAND); m_TreeCtrl.Expand (m_LevelsRoot, TVE_EXPAND); m_TreeCtrl.SetRedraw (TRUE); return ; }