//================================================================
// EQUINOX-3D(TM) plugin
//
// - Duplicate selection
// - Duplicate with repeat
//
// AUTHOR:  Gabor Nagy
// DATE:    2026-Apr-25 21:22:04
//
// Copyright (C) 1996-2026 by Gabor Nagy. All rights reserved.
//================================================================
#include <assert.h>
#include <math.h>
#include <stdlib.h>
#include <stdio.h>

#include <ELists.h>
#include <EMalloc.h>
#include <EPlugins.h>

#include <Xe/Matrix.h>
#include <Xe/Scale.h>
#include <Xe/Separator.h>
#include <Xe/PushButton.h>
#include <Xe/ToggleButton.h>

#include <Xe/Shell.h>
#include <Xe/Dialog.h>

#include <E3D/Matrix.h>
#include <E3D/Select.h>

#include <E3D/UI/Panel.h>
#include <E3D/UI/Panels.h>
#include <E3D/UI/StatusPanel.h>  // For E3dp_PrintMessage


#include "Duplicate.h"

char*  E_PluginInfo = "Duplicating\n - Copy selection\n - Duplicate with repeat\n - Duplicate over shape (Spline, Surface etc.)";

enum
{
	CR_DUPLICATE = XeCR_CUSTOM0,
	CR_INSTANCE,
	CR_SET_DUPLICATES,
	CR_SET_INSTANCE,
};


static EPlugin*      _Plugin = NULL;
static EPluginLock*  _MainDialogBoxLock = NULL;

static XeDWidget      _MenuButtons[8];static int       _MenuButtons_Count = 0;
static XeDWidget      _ToolBarButtons[2];static int  _ToolBarButtons_Count = 0;


// Duplicate
//
static XeWidget      _DialogW = NULL;

static XeWidget      _InstanceW = NULL, _ModelUniqueNamesW, _ObjectUniqueNamesW, _CopyMaterialsW, _CopyLODsW, _CopyConstructionHistoryW, _CopyAnimationsW;


static const EBool   _CopyBranchUnderSameParent = TRUE;
static const EBool   _ObjectCopyCreateNewNode = FALSE;
static EBool         _SelectNew = TRUE;


//================================
// Duplicate selection
//================================
static void _DoDuplicate(E3dScene* PScene, const EChangeFlags PFlags, const EBool PInstance, const EBool PSelectNew)
{
	EListAI_s(, E3dNodePtr, LSelectedNodes);const EIndex  LNSelectedNodes = E3dScene_GetSelectedNodes(PScene, E3dNodeSelNODE|E3dNodeSelBRANCH_ROOT|E3dNodeSelOBJECT, FALSE,  &LSelectedNodes);if(LNSelectedNodes > 0)
	{
		E3dScene*     LScene = E3d_Scene;
		EChangeFlags  LChanged = 0;
		EListAI_s(, E3dNodePtr, LNodesToSelect);

		EIndex  LMI = 0;for(;LMI < LNSelectedNodes;LMI++)
		{
			E3dNode*  LNode = LSelectedNodes.Items[LMI];
			E3dNode*  LOldRootNode = LNode;while(LOldRootNode->Parent) LOldRootNode = LOldRootNode->Parent;const EIndex  LOldRootNodeIndexInScene = EList_PointerGetIndex_s(PScene->RootNodes, LOldRootNode);

//printf("%s() %s:%d [[%s]][%s] %d\n", __FUNCTION__, __FILE__, __LINE__, LOldRootNode->Name, LNode->Name, LNode->Selection);fflush(stdout);

			switch(LNode->Selection)
			{
				case E3dNodeSelBRANCH_ROOT:
					{
						E3dNode*  LNewRootNode = E3dNodeHrc_BranchCopy(PScene, LNode, PFlags & (~E3dCopyNODE_COMPONENTS), PInstance, E3dItemALL, FALSE, TRUE);if(LNewRootNode)
						{
							if(_CopyBranchUnderSameParent && LNode->Parent)
							{
								E3dNode*  LChild = LNode->Parent->Child;
								EIndex    LIndex = 0;while(LChild != LNode && LChild->NextSibling) { LChild = LChild->NextSibling;LIndex++; }
								E3dNode_ConnectChild(LNode->Parent, LNewRootNode, LIndex + 1, FALSE, FALSE);

								E3dNodeHrc_UpdateConnections(LOldRootNode);
								E3dNodeHrc_UpdateMatrices(LOldRootNode, TRUE);
								E3dNodeHrc_UpdateMaterialInheritance(LOldRootNode);


								E3dScene_ObjectsUpdateModelList(PScene);
								E3dScene_LayoutOnSchematics(PScene, TRUE);
								E3dp_PrintMessage(ELogMESSAGE, 5000, "Branch %s cloned to %s", LNode->Name, LNewRootNode->Name);

								if(PSelectNew)
								{
									EList_PointerAddA_s(LNodesToSelect, 16, LNewRootNode);
									if(PSelectNew) LChanged |= E3dChgSELECTION;
								}

								LChanged |= E3dNodeHrc_GetTransformChangeFlagsByObjects(LNewRootNode) | E3dChgHIERARCHY;

// Clone Node info after the Matrices had been updated
//
								if(PFlags & E3dCopyCOMPONENTS) LChanged |= E3dNodeHrc_CloneInfo(PScene, LNode, LNewRootNode, E3dCopyNODE_COMPONENTS);
							}
							else
							{
								E3dScene_NodeHrcInsert(PScene, LNewRootNode, LOldRootNodeIndexInScene + 1, FALSE);
								if(!PInstance) E3dNodeHrc_UpdateForDisplay(LNewRootNode, E3dUpdateALL & ~(E3dUpdateREMOVE_STRIPS|E3dUpdateVERTEX_NORMAL));

								E3dScene_ObjectsUpdateModelList(PScene);
								E3dScene_LayoutOnSchematics(PScene, TRUE);
								E3dp_PrintMessage(ELogMESSAGE, 5000, "Branch %s cloned to %s", LNode->Name, LNewRootNode->Name);

								LChanged |= E3dNodeHrc_GetTransformChangeFlagsByObjects(LNewRootNode) | E3dChgHIERARCHY;

								if(PSelectNew)
								{
									EList_PointerAddA_s(LNodesToSelect, 16, LNewRootNode);
									LChanged |= E3dChgSELECTION;
								}

// Clone Node info after the Matrices had been updated
//
								if(PFlags & E3dCopyCOMPONENTS) LChanged |= E3dNodeHrc_CloneInfo(PScene, LNode, LNewRootNode, E3dCopyNODE_COMPONENTS);
							}
						}
					}
				break;

				case E3dNodeSelNODE:
					{
						E3dNode*  LNewNode = E3dNode_Copy(PScene, LNode, PFlags & (~E3dCopyNODE_COMPONENTS), PInstance, E3dItemALL, FALSE, FALSE);
						if(LNewNode)
						{
							if(LNode->Parent)
							{
								E3dNode*  LChild = LNode->Parent->Child;
								EIndex    LIndex = 0;while(LChild != LNode && LChild->NextSibling) { LChild = LChild->NextSibling;LIndex++; }
								E3dNode_ConnectChild(LNode->Parent, LNewNode, LIndex + 1, FALSE, FALSE);

								E3dNodeHrc_UpdateConnections(LOldRootNode);
								E3dNodeHrc_UpdateMatrices(LOldRootNode, TRUE);
								E3dNodeHrc_UpdateMaterialInheritance(LOldRootNode);
							}
							else { E3dNodeHrc_UpdateMatrices(LNewNode, TRUE);E3dScene_NodeHrcInsert(PScene, LNewNode, LOldRootNodeIndexInScene + 1, FALSE); }

							E3dScene_ObjectsUpdateModelList(PScene);
							E3dScene_LayoutOnSchematics(PScene, TRUE);
							E3dp_PrintMessage(ELogMESSAGE, 5000, "Model %s cloned to %s", LNode->Name, LNewNode->Name);

							if(PSelectNew)
							{
								EList_PointerAddA_s(LNodesToSelect, 16, LNewNode);
								if(PSelectNew) LChanged |= E3dChgSELECTION;
							}

							LChanged |= E3dNodeHrc_GetTransformChangeFlagsByObjects(LNewNode) | E3dChgHIERARCHY;

// Clone Node info after the Matrices had been updated
//
							if(PFlags & E3dCopyCOMPONENTS) LChanged |= E3dNodeHrc_CloneInfo(PScene, LNode, LNewNode, E3dCopyNODE_COMPONENTS);
						}
					}
				break;

				case E3dNodeSelOBJECT:
					if(_ObjectCopyCreateNewNode || PInstance)
					{
						E3dNode*  LNewRootNode = E3dNode_Copy(PScene, LNode, E3dp_Prefs.Duplicate_Flags, PInstance, E3dItemALL, FALSE, TRUE);if(LNewRootNode)
						{
							E3dNodeHrc_UpdateMatrices(LNewRootNode, TRUE);

							E3dScene_NodeHrcInsert(PScene, LNewRootNode, LOldRootNodeIndexInScene + 1, FALSE);

							E3dScene_ObjectsUpdateModelList(PScene);
							E3dScene_LayoutOnSchematics(PScene, TRUE);
							if(PInstance) E3dp_PrintMessage(ELogMESSAGE, 5000, "Model %s's selected Object(s) instantiated under %s", LNode->Name, LNewRootNode->Name);
							else E3dp_PrintMessage(ELogMESSAGE, 5000, "Model %s's selected Object(s) cloned to %s", LNode->Name, LNewRootNode->Name);

							LChanged |= E3dNodeHrc_GetTransformChangeFlagsByObjects(LNewRootNode) | E3dChgHIERARCHY;

							if(PSelectNew)
							{
								EList_PointerAddA_s(LNodesToSelect, 16, LNewRootNode);
								LChanged |= E3dChgSELECTION;
							}
						}
					}
					else
					{
						E3dModel*  LModel = E_DynamicCast(LNode, E3dModel);if(LModel)
						{
							EIndex  LObjI = 0;const EIndex  LObjN = LModel->Objects.Count;for(;LObjI < LObjN;LObjI++)
							{
								const E3dObject*  LObject = LModel->Objects.Items[LObjI];if(LObject->Selection == E3dObjectSelOBJECT)
								{
									E3dObject*  LNewObject = E3dObject_Copy(LObject, E3dp_Prefs.Duplicate_Flags, E3d_Scene);if(LNewObject)
									{
//printf("%s() %s:%d <%p:%s>\n", __FUNCTION__, __FILE__, __LINE__, LObject, LObject->Name);fflush(stdout);
										if(E3dp_Prefs.Duplicate_Flags & E3dCopyOBJECT_UNIQUE_NAME) EObject_SetName(LNewObject, E3dScene_GetUniqueObjectName(PScene, LObject->Name), FALSE);
										else EObject_SetName(LNewObject, EStrDup(LObject->Name), FALSE);

										E3dModel_ObjectAppend(LModel, LNewObject, FALSE);
										E3dObject_UpdateForDisplay(LNewObject,  E3dUpdateALL & ~(E3dUpdateREMOVE_STRIPS|E3dUpdateTRIANGULATE|E3dUpdateEDGES|E3dUpdateVERTEX_NORMAL), 0,  PScene);
										{ E3dMesh*  LMesh = E_DynamicCast(LNewObject, E3dMesh);if(LMesh) E3dMesh_EdgeFlagsUpdate(LMesh); }

										E3dScene_ObjectsUpdateModelList(PScene);
										E3dScene_LayoutOnSchematics(PScene, TRUE);

										LChanged |= E3dNodeHrc_GetTransformChangeFlagsByObjects(LNode) | E3dChgHIERARCHY;
									}

								}
							}
						}
					}
				break;
			}
		}

		EList_FreeA_s(LSelectedNodes);

		if(LNodesToSelect.Count > 0)
		{
// Unselect everything
//
			{
				EIndex  LI = 0;const EIndex  LN = PScene->RootNodes.Count;E3dNode**  LRootNodes = PScene->RootNodes.Items;for(;LI < LN;LI++)
				{
					E3dNode*  LNode = LRootNodes[LI];for(;LNode;LNode = LNode->Next)
					{
						LNode->Selection = E3dNodeSelNONE;
						if(EObject_IsSubClass(LNode, E3dModel)) E3dModel_ObjectsUnselect(PScene, (E3dModel*)LNode,  NULL, 0,  E3dSF_ALL, TRUE);
					}
				}
			}


			EIndex  LI = 0;const EIndex  LN = LNodesToSelect.Count;for(;LI < LN;LI++)
			{
				E3dSel_NodeSelectBranch(PScene, LNodesToSelect.Items[LI], FALSE, LI == LNodesToSelect.Count - 1);  // Only call select callbacks at the end (when LI == LNodesToSelect.Count - 1)
			}
			EList_FreeA_s(LNodesToSelect);
		}

		E3dWindows_RedrawByChangeFlags(LScene, LChanged);
		E3dTransformPanel_Update(NULL,  E3dDoALL, E3dDoALL, E3dDoALL,  E3dpDO_NOT_CHANGE,  E3dTrPanelALL);
	}
	else { E3dp_PrintMessage(ELogWARNING, 5000, "Select Model(s) or Object(s) to duplicate");return; }
}


//========================================
// Duplicate-immediate dialog callback
//========================================
static void _DCB_OK(XeWidget PW, EPointer PClientData, EPointer PCallData)
{
	_DoDuplicate(E3d_Scene, E3dp_Prefs.Duplicate_Flags, XeToggleButton_GetState(_InstanceW), _SelectNew);
	XeWidget_Unmap(_DialogW);
	EPluginLock_Del(_Plugin, &_MainDialogBoxLock, FALSE);
}


//========================================
// Duplicate-immediate dialog callback
//========================================
static void _DCB_DuplicateClose(XeWidget PW, EPointer PClientData, EPointer PCallData)
{
	XeCallbackStruct*  LCBS = (XeCallbackStruct*)PCallData;
	if(LCBS->Reason == XeCR_PROTOCOLS)
	{
		XeWidget_Unmap(_DialogW);
		EPluginLock_Del(_Plugin, &_MainDialogBoxLock, FALSE);
	}
}


//========================================
// Duplicate-immediate dialog callback
//========================================
static void _DCB_Duplicate(XeWidget PW, EPointer PClientData, EPointer PCallData)
{
	unsigned int  LFlag = (EPointerInt)PClientData;

	if(((XeToggleButtonCallbackStruct*)PCallData)->Set) E3dp_Prefs.Duplicate_Flags |= LFlag;
	else E3dp_Prefs.Duplicate_Flags &= ~LFlag;
}


//========================================
// Duplicate-immediate dialog callback
//========================================
static void _DCB_DuplicateInstance(XeWidget PW, EPointer PClientData, EPointer PCallData)
{
	XeWidget  LOptionWs[] = { _ObjectUniqueNamesW, _CopyMaterialsW, _CopyLODsW, _CopyConstructionHistoryW, _CopyAnimationsW };

	if(((XeToggleButtonCallbackStruct*)PCallData)->Set) XeWidgets_Unmanage(LOptionWs, sizeof(LOptionWs) / sizeof(XeWidget));
	else XeWidgets_Manage(LOptionWs, sizeof(LOptionWs) / sizeof(XeWidget));
}


//========================================
// Duplicate-immediate dialog callback
//========================================
static void _DCB_DuplicateSelectNew(XeWidget PW, EPointer PClientData, EPointer PCallData)
{
	_SelectNew = ((XeToggleButtonCallbackStruct*)PCallData)->Set;
}


static int _MainDialogBoxLockDel(EPlugin* PPlugin, EPluginLock* PLock)
{
	XeWidget_Unmap(_DialogW);

	return(E_SUCCESS);
}


//================================================
// Pop up the options dialog box
//================================================
static void _PopupDialog(const EBool PInstance)
{
	if(_MainDialogBoxLock == NULL) _MainDialogBoxLock = EPluginLock_Add(_Plugin, "DuplicateOptionsDialog", _MainDialogBoxLockDel);

	if(_DialogW == NULL)
	{
		EArg  Args[256];int  ArgCnt;

		EXeStart;
		EXeSetArg(XeNAllowShellResize, TRUE);
		EXeSetArg(XeNButtonsStretch, FALSE);
		EXeSetArg(XeNWorkAreaType, XeMATRIX);
		EXeSetArg(XeNWMDecorations, XeWMDecorCLOSE|XeWMDecorBORDER|XeWMDecorTITLE);
		EXeSetArg(XeNTitle, "Duplicate options");EXeSetArg(XeNIconName, "Duplicate");
		XeWidget  LDialogW = XeDialog_New(XeDIALOG_TEMPLATE, "Duplicate", Eqx_TopShellW,  Args, ArgCnt);if(LDialogW == NULL) return;
		XeAddCallback(LDialogW, XeNWMCallback, _DCB_DuplicateClose, (EPointer)0);

		_DialogW = LDialogW;

		XeWidget  LMessageBoxW = XeDialog_GetChild(LDialogW, XeDIALOG_MAIN);
		EXeStart;
		EXeSetArg(XeNWorkAreaMarginSizeX, 6);
		EXeSetArg(XeNWorkAreaMarginSizeY, 6);
		Xe_SetValues(LMessageBoxW,  Args, ArgCnt);
		{ XeWidget  LW = XeDialog_AddButton("Do it", XePushButton_class, LMessageBoxW, TRUE);XeAddCallback(LW, XeNActivateCallback, _DCB_OK, (EPointer)XeCR_OK); }


		XeWidget  LTopMatrixW = XeDialog_GetChild(LDialogW, XeDIALOG_WORK_AREA);

		EXeStart;
		EXeSetArg(XeNOrientation, XeVERTICAL);
		EXeSetArg(XeNMarginTop,   0);
		EXeSetArg(XeNMarginSizeX, 0);
		Xe_SetValues(LTopMatrixW,  Args, ArgCnt);

		XeWidget  LW;
		EXeStart;
		EXeSetArg(XeNShadowThickness, 0);
		EXeSetArg(XeNHighlightThickness, 0);
		EXeSetArg(XeNResizableY, FALSE);
		_InstanceW = LW = XeWidget_ManagedNew("Instance", XeToggleButton_class, LTopMatrixW,  Args, ArgCnt);
		XeToggleButton_SetState(LW, PInstance, FALSE);
		XeAddCallback(LW, XeNValueChangedCallback, _DCB_DuplicateInstance, (EPointer)NULL);

		LW = XeWidget_ManagedNew("Select new copy", XeToggleButton_class, LTopMatrixW,  Args, ArgCnt);
		XeAddCallback(LW, XeNValueChangedCallback, _DCB_DuplicateSelectNew, (EPointer)NULL);
		XeToggleButton_SetState(LW, _SelectNew, FALSE);

		EXeStart;
		EXeSetArg(XeNShadowThickness, 0);
		EXeSetArg(XeNHighlightThickness, 0);
		EXeSetArg(XeNSpacingTop, 8);
		EXeSetArg(XeNResizableY, FALSE);
		_ModelUniqueNamesW = LW = XeWidget_ManagedNew("Generate unique names for Models", XeToggleButton_class, LTopMatrixW,  Args, ArgCnt);
		XeAddCallback(LW, XeNValueChangedCallback, _DCB_Duplicate, (EPointer)E3dCopyMODEL_UNIQUE_NAME);
		XeToggleButton_SetState(LW, (EBool)((E3dp_Prefs.Duplicate_Flags & E3dCopyMODEL_UNIQUE_NAME) != 0), FALSE);

		EXeStart;
		EXeSetArg(XeNShadowThickness, 0);
		EXeSetArg(XeNHighlightThickness, 0);
		EXeSetArg(XeNResizableY, FALSE);
		_ObjectUniqueNamesW = LW = XeWidget_ManagedNew("Generate unique names for Objects", XeToggleButton_class, LTopMatrixW,  Args, ArgCnt);
		XeAddCallback(LW, XeNValueChangedCallback, _DCB_Duplicate, (EPointer)E3dCopyOBJECT_UNIQUE_NAME);
		XeToggleButton_SetState(LW, (EBool)((E3dp_Prefs.Duplicate_Flags & E3dCopyOBJECT_UNIQUE_NAME) != 0), FALSE);

		_CopyMaterialsW = LW = XeWidget_New("Copy Materials", XeToggleButton_class, LTopMatrixW,  Args, ArgCnt);
//		Xe_SetValuesVa(LW,  XeNMarginTop, 8,  NULL);
		XeAddCallback(LW, XeNValueChangedCallback, _DCB_Duplicate, (EPointer)E3dCopyMATERIALS);
		XeToggleButton_SetState(LW, (EBool)((E3dp_Prefs.Duplicate_Flags & E3dCopyMATERIALS) != 0), FALSE);

		_CopyLODsW = LW = XeWidget_New("Copy Geometry levels of detail", XeToggleButton_class, LTopMatrixW,  Args, ArgCnt);
		XeAddCallback(LW, XeNValueChangedCallback, _DCB_Duplicate, (EPointer)E3dCopyLODS);
		XeToggleButton_SetState(LW, (EBool)((E3dp_Prefs.Duplicate_Flags & E3dCopyLODS) != 0), FALSE);

		_CopyConstructionHistoryW = LW = XeWidget_New("Copy construction history", XeToggleButton_class, LTopMatrixW,  Args, ArgCnt);
		XeAddCallback(LW, XeNValueChangedCallback, _DCB_Duplicate, (EPointer)E3dCopyCOMPONENTS);
		XeToggleButton_SetState(LW, (EBool)((E3dp_Prefs.Duplicate_Flags & E3dCopyCOMPONENTS) != 0), FALSE);

		_CopyAnimationsW = LW = XeWidget_New("Copy animations", XeToggleButton_class, LTopMatrixW,  Args, ArgCnt);
		XeAddCallback(LW, XeNValueChangedCallback, _DCB_Duplicate, (EPointer)E3dCopyANIMATIONS);
		XeToggleButton_SetState(LW, (EBool)((E3dp_Prefs.Duplicate_Flags & E3dCopyANIMATIONS) != 0 ), FALSE);

		if(!PInstance)
		{
			XeWidget  LOptionWs[] = { _ObjectUniqueNamesW, _CopyMaterialsW, _CopyLODsW, _CopyConstructionHistoryW, _CopyAnimationsW };

			XeWidgets_Manage(LOptionWs, sizeof(LOptionWs) / sizeof(XeWidget));
		}

		XeShell_Raise(_DialogW);
		XeShell_CenterToWidget(_DialogW, Eqx_TopShellW);
	}
	else
	{
		XeWidget  LOptionWs[] = { _ObjectUniqueNamesW, _CopyMaterialsW, _CopyLODsW, _CopyConstructionHistoryW, _CopyAnimationsW };

		if(PInstance) XeWidgets_Unmanage(LOptionWs, sizeof(LOptionWs) / sizeof(XeWidget));
		else XeWidgets_Manage(LOptionWs, sizeof(LOptionWs) / sizeof(XeWidget));

		if(XeShell_DisplayStatus(_DialogW) != XeDISPLAYED)
		{
			XeToggleButton_SetState(_InstanceW, PInstance, FALSE);

			XeShell_Raise(_DialogW);
		}
	}
}


//========================================
// Duplicate menu callback
//========================================
static void _MCB(XeWidget PW, EPointer PClientData, EPointer PCallData)
{
	E3dScene*  LScene = E3d_Scene;

	switch((EPointerInt)PClientData)
	{
		case CR_DUPLICATE:
			if(((XePushButtonCallbackStruct*)PCallData)->Reason == XeCR_ACTIVATE_OPTIONS) _PopupDialog(FALSE);
			else _DoDuplicate(LScene, E3dp_Prefs.Duplicate_Flags, FALSE, _SelectNew);
		break;

		case CR_INSTANCE:
			if(((XePushButtonCallbackStruct*)PCallData)->Reason == XeCR_ACTIVATE_OPTIONS) _PopupDialog(TRUE);
			else _DoDuplicate(LScene, E3dp_Prefs.Duplicate_Flags, TRUE, _SelectNew);
		break;
	}
}


//========================================
// Key callback
//========================================
static void _KCB(const EKeyCode PKey, const unsigned int PModifiers, const int PKeyEventType, EPointer PClientData)
{
	switch((EPointerInt)PClientData)
	{
		case CR_DUPLICATE:

		case CR_INSTANCE:
			{
				XePushButtonCallbackStruct  LCBS;ECallbackStruct_Init((ECallbackStruct*)&LCBS, XePushButtonCallbackStruct_class,  XeCR_ACTIVATE);
				_MCB(NULL, PClientData, &LCBS);
			}
		break;
	}
}


//========================================
// Entry point of the plugin
//========================================
int E_PluginInit(EPlugin* PPlugin)
{
	_Plugin = PPlugin;

	if(_DuplicateWithRepeatInit())
	{
		_MenuButtons[_MenuButtons_Count++] = XePushButton_Add(Xe_App, "Menu->Clone", "Immediate", '\n', "D", "D", TRUE, NULL, _MCB, (EPointer)CR_DUPLICATE);
		E_KeySetCallback("Duplicate selection",            'D', 0,          EKeyPRESS, _KCB, (EPointer)CR_DUPLICATE);

		_MenuButtons[_MenuButtons_Count++] = XePushButton_Add(Xe_App, "Menu->Clone", "New instance", '\0', "Shift+D", "Shift+D", TRUE, NULL, _MCB, (EPointer)CR_INSTANCE);
		E_KeySetCallback("Duplicate: create new instance", 'D', EShiftMask, EKeyPRESS, _KCB, (EPointer)CR_INSTANCE);
	}

	return(E_SUCCESS);
}


//========================================
// Exit method of the plugin
//========================================
int E_PluginExit(const EBool PExiting)
{
	if(!PExiting || Xe_GUIFreeOnExit)
	{
		int  LI;

		for(LI = 0;LI < _MenuButtons_Count;LI++) { XeItemFree(_MenuButtons[LI]); } _MenuButtons_Count = 0;
		for(LI = 0;LI < _ToolBarButtons_Count;LI++) { XeItemFree(_ToolBarButtons[LI]); } _ToolBarButtons_Count = 0;
	}

	E_KeyRemoveCallback('D', 0,          EKeyPRESS, _KCB, (EPointer)CR_DUPLICATE);
	E_KeyRemoveCallback('D', EShiftMask, EKeyPRESS, _KCB, (EPointer)CR_INSTANCE);

	_DuplicateWithRepeatFree();

	return(E_SUCCESS);
}
