/* ** 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 . */ #include "clipboard.h" #include #include "utilapi.h" //---------------------------------------------------------------------------- // Node_Key //---------------------------------------------------------------------------- class Node_Key { public: Node_Key ( int nr_frames, const char * name, Node_Key * next ); ~Node_Key (); void set_position ( int frame, Point3 pos ) { if ( frame >= 0 && frame < nr_frames_ ) position_ [frame] = pos; } void set_orientation ( int frame, Quat facing ) { if ( frame >= 0 && frame < nr_frames_ ) orientation_ [frame] = facing; } const Point3 & position ( int frame ) { if ( frame < 0 || frame >= nr_frames_ ) frame = 0; return position_ [frame]; } const Quat & orientation ( int frame ) { if ( frame < 0 || frame >= nr_frames_ ) frame = 0; return orientation_ [frame]; } const char * name () { return name_; } int nr_frames () { return nr_frames_; } Node_Key * next () { return next_; } private: char * name_; int nr_frames_; Point3 * position_; Quat * orientation_; Node_Key * next_; }; //---------------------------------------------------------------------------- // Node_Key::Node_Key //---------------------------------------------------------------------------- Node_Key::Node_Key ( int nr_frames, const char * name, Node_Key * next ): nr_frames_ (nr_frames), next_ (next) { position_ = new Point3 [ nr_frames ]; orientation_ = new Quat [ nr_frames ]; name_ = new char [ strlen (name) + 1 ]; strcpy ( name_, name ); } //---------------------------------------------------------------------------- // Node_Key::~Node_Key //---------------------------------------------------------------------------- Node_Key::~Node_Key () { delete [] position_; delete [] orientation_; delete [] name_; } //---------------------------------------------------------------------------- // Pose //---------------------------------------------------------------------------- const MAX_NAME = 64; class Pose { public: Pose ( Interface * ip, const char * new_name, int first_frame, int last_frame ); ~Pose () { delete_keys (); } const char * name () const { return name_; } void Add_Objects_To_List ( HWND hWnd ); void Paste_Keys ( Interface * ip ); Pose * next; private: void delete_keys (); Node_Key * first_key; char name_ [ MAX_NAME ]; int ticks_per_frame_; }; //---------------------------------------------------------------------------- // Clipboard_Class //---------------------------------------------------------------------------- class Clipboard_Class : public UtilityObj { public: Clipboard_Class (); ~Clipboard_Class (); void BeginEditParams(Interface *ip,IUtil *iu); void EndEditParams(Interface *ip,IUtil *iu); void SelectionSetChanged ( Interface * ip, IUtil * iu ); void DeleteThis() {} void Init(HWND hWnd); void Destroy(HWND hWnd); void Close () { iu->CloseUtility (); } void Create ( HWND ); void Paste ( HWND ); void Delete ( HWND ); BOOL Is_Empty () const { return first_pose == NULL; } void Update_Object_List ( HWND hWnd ); private: void Update_Pose_List ( HWND hWnd ); IUtil * iu; Interface *ip; HWND hPanel; Pose * first_pose; }; //---------------------------------------------------------------------------- // the_clipboard //---------------------------------------------------------------------------- static Clipboard_Class the_clipboard; //---------------------------------------------------------------------------- // Static data used for communicating to/from the name dialog. //---------------------------------------------------------------------------- static char pose_name [ MAX_NAME ]; static int first_frame; static int last_frame; static int current_frame; //---------------------------------------------------------------------------- // Clipboard_Desc_Class //---------------------------------------------------------------------------- class Clipboard_Desc_Class:public ClassDesc { public: int IsPublic() {return 1;} void * Create(BOOL) {return &the_clipboard;} const TCHAR * ClassName() {return _T("Key Clipboard");} SClass_ID SuperClassID() {return UTILITY_CLASS_ID;} Class_ID ClassID() {return Class_ID(0x5eb13907, 0x1d931bb4);} const TCHAR* Category() {return _T("Westwood Tools");} }; //---------------------------------------------------------------------------- // clipboard_desc //---------------------------------------------------------------------------- static Clipboard_Desc_Class clipboard_desc; //---------------------------------------------------------------------------- // ClipboardDesc //---------------------------------------------------------------------------- ClassDesc* ClipboardDesc() {return &clipboard_desc;} //---------------------------------------------------------------------------- // ClipboardDlgProc //---------------------------------------------------------------------------- static BOOL CALLBACK ClipboardDlgProc ( HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam ) { switch (msg) { case WM_INITDIALOG: the_clipboard.Init(hWnd); break; case WM_DESTROY: the_clipboard.Destroy(hWnd); break; case WM_COMMAND: switch (LOWORD(wParam)) { case ID_CLOSE: the_clipboard.Close (); break; case ID_CREATE: the_clipboard.Create ( hWnd ); break; case ID_PASTE: the_clipboard.Paste ( hWnd ); break; case ID_DELETE: the_clipboard.Delete ( hWnd ); break; case IDC_POSE_LIST: if ( HIWORD(wParam) == CBN_SELCHANGE ) the_clipboard.Update_Object_List ( hWnd ); break; } break; default: return FALSE; } return TRUE; } //---------------------------------------------------------------------------- // Pose_Name_Message_Handler //---------------------------------------------------------------------------- static BOOL CALLBACK Pose_Name_Message_Handler ( HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam ) { static ISpinnerControl * first_frame_spin; static ISpinnerControl * last_frame_spin; switch (msg) { case WM_INITDIALOG: CenterWindow ( hWnd, GetParent (hWnd) ); SetFocus ( GetDlgItem (hWnd, IDC_NAME) ); first_frame_spin = SetupIntSpinner ( hWnd, IDC_FIRST_FRAME_SPIN, IDC_FIRST_FRAME_EDIT, first_frame, last_frame, current_frame ); first_frame_spin->SetResetValue ( current_frame ); last_frame_spin = SetupIntSpinner ( hWnd, IDC_LAST_FRAME_SPIN, IDC_LAST_FRAME_EDIT, first_frame, last_frame, current_frame ); last_frame_spin->SetResetValue ( current_frame ); return TRUE; case WM_COMMAND: switch (LOWORD(wParam)) { case IDOK: if ( SendDlgItemMessage ( hWnd, IDC_NAME, WM_GETTEXTLENGTH, 0, 0 ) == 0 ) EndDialog ( hWnd, 0 ); SendDlgItemMessage ( hWnd, IDC_NAME, WM_GETTEXT, MAX_NAME, (LPARAM) pose_name ); first_frame = first_frame_spin->GetIVal (); last_frame = last_frame_spin->GetIVal (); EndDialog ( hWnd, 1 ); break; case IDCANCEL: EndDialog ( hWnd, 0 ); break; } return TRUE; case CC_SPINNER_CHANGE: // Do checking on the range spinners to make sure the low value never // exceeds the high value. switch ( LOWORD(wParam) ) { case IDC_FIRST_FRAME_SPIN: if ( first_frame_spin->GetIVal () > last_frame_spin->GetIVal () ) { last_frame_spin->SetValue ( first_frame_spin->GetIVal (), FALSE ); } break; case IDC_LAST_FRAME_SPIN: if ( last_frame_spin->GetIVal () < first_frame_spin->GetIVal () ) { first_frame_spin->SetValue ( last_frame_spin->GetIVal (), FALSE ); } break; } return TRUE; } return FALSE; } //---------------------------------------------------------------------------- // Clipboard_Class::Clipboard_Class //---------------------------------------------------------------------------- Clipboard_Class::Clipboard_Class() { iu = NULL; ip = NULL; hPanel = NULL; first_pose = NULL; } //---------------------------------------------------------------------------- // Clipboard_Class::~Clipboard_Class //---------------------------------------------------------------------------- Clipboard_Class::~Clipboard_Class () { while ( first_pose != NULL ) { Pose * delete_p = first_pose; first_pose = first_pose->next; delete delete_p; } } //---------------------------------------------------------------------------- // Clipboard_Class::BeginEditParams //---------------------------------------------------------------------------- void Clipboard_Class::BeginEditParams(Interface *ip,IUtil *iu) { this->iu = iu; this->ip = ip; hPanel = ip->AddRollupPage ( hInstance, MAKEINTRESOURCE(IDD_CLIPBOARD_PANEL), ClipboardDlgProc, _T("Key Clipboard"), 0 ); } //---------------------------------------------------------------------------- // Clipboard_Class::EndEditParams //---------------------------------------------------------------------------- void Clipboard_Class::EndEditParams ( Interface *ip, IUtil *iu ) { this->iu = NULL; this->ip = NULL; ip->DeleteRollupPage(hPanel); hPanel = NULL; } //---------------------------------------------------------------------------- // Clipboard_Class::SelectionSetChanged //---------------------------------------------------------------------------- void Clipboard_Class::SelectionSetChanged ( Interface * ip, IUtil * iu ) { if ( ip->GetSelNodeCount () == 0 ) { EnableWindow ( GetDlgItem ( hPanel, ID_CREATE ), FALSE ); } else { EnableWindow ( GetDlgItem ( hPanel, ID_CREATE ), TRUE ); } } //---------------------------------------------------------------------------- // Clipboard_Class::Init //---------------------------------------------------------------------------- void Clipboard_Class::Init ( HWND hWnd ) { Update_Pose_List ( hWnd ); SendDlgItemMessage ( hWnd, IDC_POSE_LIST, CB_SETCURSEL, 0, 0 ); Update_Object_List ( hWnd ); if ( ip->GetSelNodeCount () == 0 ) { EnableWindow ( GetDlgItem ( hWnd, ID_CREATE ), FALSE ); } else { EnableWindow ( GetDlgItem ( hWnd, ID_CREATE ), TRUE ); } if ( Is_Empty () ) { EnableWindow ( GetDlgItem ( hWnd, ID_PASTE ), FALSE ); EnableWindow ( GetDlgItem ( hWnd, ID_DELETE ), FALSE ); } else { EnableWindow ( GetDlgItem ( hWnd, ID_PASTE ), TRUE ); EnableWindow ( GetDlgItem ( hWnd, ID_DELETE ), TRUE ); } } //---------------------------------------------------------------------------- // Clipboard_Class::Destroy //---------------------------------------------------------------------------- void Clipboard_Class::Destroy(HWND hWnd) {} //---------------------------------------------------------------------------- // Clipboard_Class::Create //---------------------------------------------------------------------------- void Clipboard_Class::Create ( HWND hWnd ) { // Set up the data that will appear in the name dialog. const int ticks_per_frame = GetTicksPerFrame (); first_frame = ip->GetAnimRange ().Start () / ticks_per_frame; last_frame = ip->GetAnimRange ().End () / ticks_per_frame; current_frame = ip->GetTime () / ticks_per_frame; // Put up the name dialog. int rv = DialogBoxParam ( hInstance, MAKEINTRESOURCE ( IDD_POSE_NAME ), hWnd, Pose_Name_Message_Handler, 0 ); if ( rv == 0 ) return; Pose * new_pose = new Pose ( ip, pose_name, first_frame, last_frame ); new_pose->next = first_pose; first_pose = new_pose; Update_Pose_List ( hWnd ); SendDlgItemMessage ( hWnd, IDC_POSE_LIST, CB_SETCURSEL, 0, 0 ); Update_Object_List ( hWnd ); EnableWindow ( GetDlgItem ( hWnd, ID_PASTE ), TRUE ); EnableWindow ( GetDlgItem ( hWnd, ID_DELETE ), TRUE ); } //---------------------------------------------------------------------------- // Clipboard_Class::Paste //---------------------------------------------------------------------------- void Clipboard_Class::Paste ( HWND hWnd ) { SetCursor ( LoadCursor (NULL, IDC_WAIT) ); int sel = SendDlgItemMessage ( hWnd, IDC_POSE_LIST, CB_GETCURSEL, 0, 0 ); if ( sel == CB_ERR ) return; // Find the selected pose. Pose * p = first_pose; while ( sel > 0 && p != NULL ) { p = p->next; -- sel; } if ( p == NULL ) return; p->Paste_Keys ( ip ); SetCursor ( LoadCursor (NULL, IDC_ARROW) ); } //---------------------------------------------------------------------------- // Clipboard_Class::Delete //---------------------------------------------------------------------------- void Clipboard_Class::Delete ( HWND hWnd ) { int sel = SendDlgItemMessage ( hWnd, IDC_POSE_LIST, CB_GETCURSEL, 0, 0 ); if ( sel != CB_ERR ) { // Delete the pose's name from the list box. SendDlgItemMessage ( hWnd, IDC_POSE_LIST, CB_DELETESTRING, sel, 0 ); SendDlgItemMessage ( hWnd, IDC_POSE_LIST, CB_SETCURSEL, 0, 0 ); // Find the selected pose and delete it. Pose ** pointer_to_p = & first_pose; Pose * p = first_pose; while ( sel > 0 && p != NULL ) { pointer_to_p = & p->next; p = p->next; -- sel; } if ( p != NULL ) { *pointer_to_p = p->next; delete p; } Update_Object_List ( hWnd ); } if ( Is_Empty () ) { EnableWindow ( GetDlgItem ( hWnd, ID_PASTE ), FALSE ); EnableWindow ( GetDlgItem ( hWnd, ID_DELETE ), FALSE ); } } //---------------------------------------------------------------------------- // Clipboard_Class::Update_Pose_List //---------------------------------------------------------------------------- void Clipboard_Class::Update_Pose_List ( HWND hWnd ) { // Delete any strings in the combo box. SendDlgItemMessage ( hWnd, IDC_POSE_LIST, CB_RESETCONTENT, 0, 0 ); // Rebuild the combo box. for ( Pose * p = first_pose; p != NULL; p = p->next ) { SendDlgItemMessage ( hWnd, IDC_POSE_LIST, CB_ADDSTRING, 0, (LPARAM) (LPCTSTR) p->name () ); } } //---------------------------------------------------------------------------- // Clipboard_Class::Update_Object_List //---------------------------------------------------------------------------- void Clipboard_Class::Update_Object_List ( HWND hWnd ) { // Delete any strings in the combo box. SendDlgItemMessage ( hWnd, IDC_OBJECT_LIST, LB_RESETCONTENT, 0, 0 ); // Add strings from the objects in the currently selected pose. int sel = SendDlgItemMessage ( hWnd, IDC_POSE_LIST, CB_GETCURSEL, 0, 0 ); if ( sel != CB_ERR ) { Pose * p = first_pose; while ( sel > 0 && p != NULL ) { p = p->next; -- sel; } if ( p != NULL ) { p->Add_Objects_To_List ( hWnd ); } } } //---------------------------------------------------------------------------- // Pose::Pose //---------------------------------------------------------------------------- Pose::Pose ( Interface * ip, const char * name, int first_frame, int last_frame ): first_key (NULL), next (NULL), ticks_per_frame_ (GetTicksPerFrame ()) { int number_of_nodes = ip->GetSelNodeCount (); int nr_frames = (last_frame - first_frame) + 1; TimeValue start_time = first_frame * ticks_per_frame_; char frame_count [ 16 ]; sprintf ( frame_count, " (%d)", nr_frames ); strncpy ( name_, name, MAX_NAME - 6 ); strncat ( name_, frame_count, 5 ); name_ [MAX_NAME - 1] = '\0'; for ( int i = 0; i < number_of_nodes; ++ i ) { // Get the inode. INode * inode = ip->GetSelNode ( i ); Control * tm_controller = inode->GetTMController (); if ( tm_controller == NULL ) continue; Node_Key * new_key = new Node_Key ( nr_frames, inode->GetName (), first_key ); first_key = new_key; // Store the position and rotation keys. Point3 node_position ( 0.0, 0.0, 0.0 ); Quat node_orientation ( 0.0, 0.0, 0.0, 1.0 ); for ( int frame = 0; frame < nr_frames; ++ frame ) { TimeValue key_time = frame * ticks_per_frame_ + start_time; Control * c; // Copy rotation keys. c = tm_controller->GetRotationController (); if ( c != NULL ) c->GetValue ( key_time, & node_orientation, FOREVER ); else node_orientation = Quat (0.0, 0.0, 0.0, 1.0); // Copy position keys. c = tm_controller->GetPositionController (); if ( c != NULL ) c->GetValue ( key_time, & node_position, FOREVER ); else node_position = Point3 (0.0, 0.0, 0.0); new_key->set_position ( frame, node_position ); new_key->set_orientation ( frame, node_orientation ); } } } //---------------------------------------------------------------------------- // Pose::Paste_Keys //---------------------------------------------------------------------------- void Pose::Paste_Keys ( Interface * ip ) { TimeValue current_time = ip->GetTime (); theHold.Begin (); SuspendAnimate (); AnimateOn (); Node_Key * p = first_key; while ( p != NULL ) { INode * inode = ip->GetINodeByName ( p->name () ); if ( inode != NULL ) { Control * tm_controller = inode->GetTMController (); if ( tm_controller != NULL ) { for ( int frame = 0; frame < p->nr_frames (); ++ frame ) { TimeValue key_time = current_time + frame * ticks_per_frame_; Control * c; // Paste rotation keys. c = tm_controller->GetRotationController (); if ( c != NULL ) { c->EnableORTs (FALSE); Quat orientation = p->orientation (frame); c->SetValue ( key_time, & orientation ); c->EnableORTs (TRUE); } // Paste position keys. c = tm_controller->GetPositionController (); if ( c != NULL ) { c->EnableORTs (FALSE); Point3 position = p->position (frame); c->SetValue ( key_time, & position ); c->EnableORTs (TRUE); } } } } p = p->next (); } ResumeAnimate (); TSTR undostr; undostr.printf ( "Paste Keys" ); theHold.Accept ( undostr ); ip->RedrawViews ( current_time ); } //---------------------------------------------------------------------------- // Pose::delete_keys //---------------------------------------------------------------------------- void Pose::delete_keys () { while ( first_key != NULL ) { Node_Key * delete_p = first_key; first_key = first_key->next (); delete delete_p; } } //---------------------------------------------------------------------------- // Pose::Add_Objects_To_List //---------------------------------------------------------------------------- void Pose::Add_Objects_To_List ( HWND hWnd ) { for ( Node_Key * k = first_key; k != NULL; k = k->next () ) { SendDlgItemMessage ( hWnd, IDC_OBJECT_LIST, LB_ADDSTRING, 0, (LPARAM) (LPCTSTR) k->name () ); } }