/*=========================================================================
*
* CPABase.cpp :  CPA_EditorBase : Implementation file.
*
*
* Version			1.0
* Creation date
* Revision date
*
* Shaitan
*=======================================================================*/

#include "stdafx.h"
#include "Itf/customid.h"

#ifdef ACTIVE_EDITOR
#include "acp_base.h"

#include "itf/CPABase.hpp"
#include "itf/CPAMWorl.hpp"
#include "itf/CPAProj.hpp"


/*===========================================================================
	Constructor
=========================================================================*/
CPA_EditorBase::CPA_EditorBase (void)
{
	m_oListOfReachableObjectList.RemoveAll();
    m_oListOfModifiedReachableObjects.RemoveAll();
	m_p_oMainWorld = NULL;
	m_stContext.pPreviousEditor = NULL;
	// editor info
	m_stEditorInfo.csName		= "";
	m_stEditorInfo.csAuthor		= "";
	m_stEditorInfo.csVersion	= "";
	m_stEditorInfo.csHelpFile	= "";
}

CPA_EditorBase::~CPA_EditorBase (void)
{
	CPA_ReachableObjectList *p_oList;

	while (!(m_oListOfReachableObjectList.IsEmpty()))
	{
		p_oList = m_oListOfReachableObjectList.GetHead();
		p_oList->DeleteAllObjects();
		delete m_oListOfReachableObjectList.RemoveHead();
	}

    POSITION stPos = m_oListOfModifiedReachableObjects.GetHeadPosition();
    while ( stPos )
    {
        tdstModifiedObjectsInfo *p_stList = m_oListOfModifiedReachableObjects.GetNext( stPos );
        POSITION stPosROLN = p_stList -> m_oListOfModifiedObjects . GetHeadPosition();
        while ( stPosROLN )
        {
            tdstReachableObjectListNamed *p_stROLN = p_stList -> m_oListOfModifiedObjects . GetNext( stPosROLN );
            p_stROLN -> m_oListOfReachableObjects . RemoveAll();
            delete p_stROLN;
        }
        p_stList -> m_oListOfModifiedObjects . RemoveAll();
        delete p_stList;
    }
}

void CPA_EditorBase::SetEditorInfo(CString csName, CString csAuthor,
                                   CString csVersion, CString csHelpFile)
{
    m_stEditorInfo . csName = csName;
    m_stEditorInfo . csAuthor = csAuthor;
    m_stEditorInfo . csVersion = csVersion;
    m_stEditorInfo . csHelpFile = csHelpFile;
}

void CPA_EditorBase::SetMainWorld (CPA_MainWorld *p_oMainWorld)
{
	m_p_oMainWorld = p_oMainWorld;
	if (m_p_oMainWorld)
		m_p_oMainWorld->fn_vRegisterEditor(this);
}

CPA_Interface * CPA_EditorBase::GetInterface (void)
{
	return m_p_oMainWorld->GetInterface();
}

/*===========================================================================
	Register objects types for ReachableObjectsList
=========================================================================*/
void CPA_EditorBase::fn_vRegisterObjectsType (CString a_csTypeName[], long lTypeCount)
{
	long iIndex;

	for (iIndex = 0; iIndex < lTypeCount; iIndex++)
	{
		CPA_ReachableObjectList *p_oListOfObjects;

		// check if there is allready a list with that typename!!!!
		ASSERT(m_p_oMainWorld->fn_p_oGetOriginalObjectList(a_csTypeName[iIndex]) == NULL);

		p_oListOfObjects = new CPA_ReachableObjectList(a_csTypeName[iIndex]);
		m_oListOfReachableObjectList.AddTail(p_oListOfObjects);
	}
}

/*===========================================================================
=========================================================================*/
long CPA_EditorBase::fn_lFillWithRegisterObjectsType(CPA_List<CString> *_p_oList)
{
    POSITION stPos = m_oListOfReachableObjectList.GetHeadPosition();
    while ( stPos )
    {
        _p_oList -> AddTail( m_oListOfReachableObjectList.GetNext( stPos ) -> fn_p_csGetTypeName() );
    }
    return ((long)m_oListOfReachableObjectList.GetCount());
}

/*===========================================================================
	Get the ReachableObjectList corresponding to csTypeName
=========================================================================*/
CPA_ReachableObjectList * CPA_EditorBase::GetObjectList (CString csTypeName)
{
	CPA_ReachableObjectList *p_oListOfObjects;
    POSITION				pos;

	pos = m_oListOfReachableObjectList.GetHeadPosition();

    // search list
    while(pos)
    {
        p_oListOfObjects = m_oListOfReachableObjectList.GetNext(pos);
        if (p_oListOfObjects->fn_bIsType(csTypeName))
			return p_oListOfObjects;
    }
	return NULL;
}


/*===========================================================================
	Add object to the corresponding ReachableObjectList
=========================================================================*/
BOOL CPA_EditorBase::fn_bAddObject (CPA_ReachableObject *p_oObject)
{
    CPA_ReachableObjectList *p_oListOfObjects;

	// find the list
	p_oListOfObjects = GetObjectList(p_oObject->CPA_ReachableObject::fn_csGetTypeName());
	// if the list for this type of object does not exist
	if (!p_oListOfObjects)
	{
		ASSERT(0);
		return FALSE;
	}
	// add object
	return p_oListOfObjects->fn_bAddObject(p_oObject);
}


/*===========================================================================
	Remove object from the corresponding ReachableObjectList
=========================================================================*/
BOOL CPA_EditorBase::fn_bRemoveObject (CPA_ReachableObject *p_oObject)
{
    CPA_ReachableObjectList *p_oListOfObjects;

	// find corresponding list
	p_oListOfObjects = GetObjectList(p_oObject->CPA_ReachableObject::fn_csGetTypeName());
	// type of object does not exists in this DLL
	if (p_oListOfObjects == NULL)
    {
		ASSERT(0);
		return FALSE;
	}

	return p_oListOfObjects->fn_bRemoveObject(p_oObject);
}


/*===========================================================================
	check if object exists
=========================================================================*/
BOOL CPA_EditorBase::fn_bExist(CPA_ReachableObject *p_oObject)
{
    CPA_ReachableObjectList *p_oListOfObjects;
    BOOL					bResult = FALSE;

    p_oListOfObjects = GetObjectList(p_oObject->CPA_ReachableObject::fn_csGetTypeName());

    if(p_oListOfObjects)
        bResult = p_oListOfObjects->fn_bExist(p_oObject);

    return bResult;
}


/*===========================================================================
	check if object exists
=========================================================================*/
BOOL CPA_EditorBase::fn_bExist(CString csObjectName, CString csObjectType, CPA_ReachableObject *p_oOwner)
{
	CPA_ReachableObjectList *p_oListOfObjects;
	POSITION				pos;

	if ((csObjectName.IsEmpty()) && (csObjectType.IsEmpty()) && (p_oOwner == NO_CRITERIA))
	{
		ASSERT(0);
	}

	// if type is valid
	if (!csObjectType.IsEmpty())
	{
		// we just look in the list corresponding to this type
		p_oListOfObjects = GetObjectList(csObjectType);
		if (!p_oListOfObjects)
			return FALSE;

		return p_oListOfObjects->fn_bExist(csObjectName,csObjectType,p_oOwner);
	}
	else
	{
		// we must look in every list
		pos = m_oListOfReachableObjectList.GetHeadPosition();
		while(pos) // look in every list
		{
			// get the current list
			p_oListOfObjects = m_oListOfReachableObjectList.GetNext(pos);
			// find the object in this list
			if (p_oListOfObjects->fn_bExist(csObjectName,csObjectType,p_oOwner))
				return TRUE;
		}
	}
	return FALSE;
}


/*===========================================================================
	find object from its type and name
=========================================================================*/
CPA_ReachableObject * CPA_EditorBase::GetReachableObject (CString csObjectName, CString csObjectType, CPA_ReachableObject *p_oOwner)
{
	CPA_ReachableObjectList *p_oListOfObjects;
	CPA_ReachableObject		*p_oObject;
	POSITION				pos;


	if ((csObjectName.IsEmpty()) && (csObjectType.IsEmpty()) && (p_oOwner == NO_CRITERIA))
	{
		ASSERT(0);
	}

	// if type is valid
	if (!csObjectType.IsEmpty())
	{
		// we just look in the list corresponding to this type
		p_oListOfObjects = GetObjectList(csObjectType);
		if (!p_oListOfObjects)
			return NULL;

		return p_oListOfObjects->fn_p_oFindObject(csObjectName,csObjectType,p_oOwner);
	}
	else
	{
		// we must look in every list
		pos = m_oListOfReachableObjectList.GetHeadPosition();
		while (pos)
		{
			// get the current list
			p_oListOfObjects = m_oListOfReachableObjectList.GetNext(pos);
			// find the object in this list
			p_oObject = p_oListOfObjects->fn_p_oFindObject(csObjectName,csObjectType,p_oOwner);
			if (p_oObject != NULL)
				return p_oObject;
		}
	}
	return NULL;
}


/*===========================================================================
	find object from its type and engine struct
=========================================================================*/
CPA_ReachableObject * CPA_EditorBase::GetReachableObject (void *p_vEngineStruct, CString csObjectType, CPA_ReachableObject *p_oOwner)
{
	CPA_ReachableObjectList *p_oListOfObjects;
	CPA_ReachableObject		*p_oObject;
	POSITION				pos;


	if ((p_vEngineStruct == NULL) && (csObjectType.IsEmpty()) && (p_oOwner == NO_CRITERIA))
	{
		ASSERT(0);
	}

	// if type is valid
	if (!csObjectType.IsEmpty())
	{
		// we just look in the list corresponding to this type
		p_oListOfObjects = GetObjectList(csObjectType);
		if (!p_oListOfObjects)
			return NULL;

		return p_oListOfObjects->fn_p_oFindObjectWithEngine(p_vEngineStruct,csObjectType,p_oOwner);
	}
	else
	{
		// we must look in every list
		pos = m_oListOfReachableObjectList.GetHeadPosition();
		while (pos)
		{
			CPA_ReachableObjectList *p_oListOfObjects;

			// get the current list
			p_oListOfObjects = m_oListOfReachableObjectList.GetNext(pos);
			// find the object in this list
			p_oObject = p_oListOfObjects->fn_p_oFindObjectWithEngine(p_vEngineStruct,csObjectType,p_oOwner);
			if  (p_oObject != NULL)
				return p_oObject;
		}
	}
	return NULL;
}

/*===========================================================================
	find objects with given type and name
=========================================================================*/
long CPA_EditorBase::fn_lFindObjects (CPA_ReachableObjectList *p_oResultList, CString csObjectName, CString csObjectType, CPA_ReachableObject *p_oOwner)
{
	CPA_ReachableObjectList *p_oListOfObjects;
	POSITION				pos;
    long					lResult = 0;


	if ((csObjectName.IsEmpty()) && (csObjectType.IsEmpty()) && (p_oOwner == NO_CRITERIA))
	{
		ASSERT(0);
	}

	// if type is valid
	if (!csObjectType.IsEmpty())
	{
		// we just look in the list corresponding to this type
		p_oListOfObjects = GetObjectList(csObjectType);
		if(!p_oListOfObjects)
			return 0;

		lResult = p_oListOfObjects->fn_lFindObjects(p_oResultList,csObjectName,csObjectType,p_oOwner);
	}
	else
	{
		// we must look in every list
		pos = m_oListOfReachableObjectList.GetHeadPosition();
		while(pos) // look in every list, whatever the result is
		{
			// get the current list
			p_oListOfObjects = m_oListOfReachableObjectList.GetNext(pos);
			// find the object in this list
			lResult += p_oListOfObjects->fn_lFindObjects(p_oResultList,csObjectName,csObjectType,p_oOwner);
		}
	}
	return lResult;
}



/*===========================================================================
	update lists when objects are created or deleted
=========================================================================*/
void CPA_EditorBase::fn_vUpdateObjectList (CPA_ReachableObject *p_oObject, tdeUpdate eType)
{
	CPA_ReachableObject *pEdObj;

	// delete => unregister object
	if (eType == C_EDTDelete)
		p_oObject->fn_bUnRegister();
	// insert => register object
	else
	{
		// is pEdObj already registered ?
		pEdObj = GetReachableObject(p_oObject->GetName(), p_oObject->fn_csGetTypeName(), p_oObject->GetOwner());
		// register object (if necessary, change its name)
		if (pEdObj != p_oObject)
			p_oObject->GetMainWorld()->fn_p_cReInsertAnObject(p_oObject, (eType == C_EDTInsert));
	}
}

/////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// for Modified Reachable Objects
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void CPA_EditorBase::fn_vRegisterModificationsType(CString _csObjectTypeName, CString a_csModificationType[], long lTypeCount)
{
    ASSERT( GetObjectList( _csObjectTypeName ) );

    tdstModifiedObjectsInfo *p_stList = fn_p_stGetMOInfo( _csObjectTypeName );
    if ( !p_stList )
    {
        // create new
        p_stList = new(tdstModifiedObjectsInfo);
        p_stList -> m_oObjectType = _csObjectTypeName;
        p_stList-> m_oListOfModifiedObjects . RemoveAll();
        // add
        m_oListOfModifiedReachableObjects . AddTail( p_stList );
    }
    // add modifications
    for ( long i=0 ; i<lTypeCount ; i++ )
    {
        ASSERT( !fn_bIsRegisterModificationType( p_stList, a_csModificationType[i] ) );

        tdstReachableObjectListNamed *p_stROLN = new(tdstReachableObjectListNamed);
        p_stROLN -> m_ocsModificationTypeName = a_csModificationType[i];
        p_stROLN -> m_oListOfReachableObjects . RemoveAll();
        p_stROLN -> m_oListOfReachableObjects . fn_bSetTypeName( _csObjectTypeName );
        p_stList -> m_oListOfModifiedObjects . AddTail( p_stROLN );
    }
}

/*===========================================================================
=========================================================================*/
long CPA_EditorBase::fn_lFillWithRegisterModificationsType(CPA_List<CString> *_p_oList, CString _csObjectTypeName)
{
    ASSERT( GetObjectList( _csObjectTypeName ) );

    tdstModifiedObjectsInfo *p_stList = fn_p_stGetMOInfo( _csObjectTypeName );

    if( p_stList )
    {
        POSITION stPos = p_stList->m_oListOfModifiedObjects.GetHeadPosition();
        while ( stPos )
        {
            _p_oList -> AddTail( &(p_stList -> m_oListOfModifiedObjects . GetNext( stPos ) -> m_ocsModificationTypeName) );
        }
        return ((long)p_stList->m_oListOfModifiedObjects.GetCount());
    }
    return 0;
}

/*===========================================================================
=========================================================================*/
int CPA_EditorBase::fn_iGetModificationCount(CString _csObjectTypeName)
{
    tdstModifiedObjectsInfo *p_stList = fn_p_stGetMOInfo( _csObjectTypeName );
    return ( p_stList ? p_stList -> m_oListOfModifiedObjects . GetCount() : 0 );
}

/*===========================================================================
=========================================================================*/
CPA_ReachableObjectList* CPA_EditorBase::fn_p_oGetModifiedObjectsList(CString _csObjectTypeName, CString _csModificationType)
{
    tdstReachableObjectListNamed *p_stROLN = fn_p_stGetROLN (_csObjectTypeName, _csModificationType);
    return ( p_stROLN ? &(p_stROLN -> m_oListOfReachableObjects) : NULL );
}

/*===========================================================================
=========================================================================*/
int CPA_EditorBase::fn_iGetModificationIndex(CString _csObjectTypeName, CString _csModificationType)
{
    tdstModifiedObjectsInfo *p_stList = fn_p_stGetMOInfo( _csObjectTypeName );
    if(p_stList)
    {
        int iResult = 0;
        POSITION stPos = p_stList -> m_oListOfModifiedObjects . GetHeadPosition();
        while ( stPos )
        {
            if( p_stList -> m_oListOfModifiedObjects . GetNext( stPos ) -> m_ocsModificationTypeName == _csModificationType )
                return iResult;
            iResult++;
        }
    }
    return -1;
}

/*===========================================================================
=========================================================================*/
CString CPA_EditorBase::fn_csGetModificationByIndex(CString _csObjectTypeName, int _iIndex )
{
    tdstModifiedObjectsInfo *p_stList = fn_p_stGetMOInfo( _csObjectTypeName );
    if(p_stList)
    {
        POSITION stPos = p_stList -> m_oListOfModifiedObjects . FindIndex( _iIndex );
        return ( stPos ? p_stList -> m_oListOfModifiedObjects . GetAt( stPos ) -> m_ocsModificationTypeName : "");
    }
    return "";
}

/*===========================================================================
=========================================================================*/
BOOL CPA_EditorBase::fn_bAddModifiedObject(CPA_ReachableObject *_p_oObject, CString _csModificationType)
{
    tdstReachableObjectListNamed *p_stROLN = fn_p_stGetROLN (_p_oObject -> fn_csGetTypeName(), _csModificationType);
    if ( p_stROLN )
    {
        return p_stROLN -> m_oListOfReachableObjects . fn_bAddObject(_p_oObject);
    }
    return FALSE;
}

/*===========================================================================
=========================================================================*/
BOOL CPA_EditorBase::fn_bRemoveModifiedObject(CPA_ReachableObject *_p_oObject, CString _csModificationType)
{
    tdstReachableObjectListNamed *p_stROLN = fn_p_stGetROLN (_p_oObject -> fn_csGetTypeName(), _csModificationType);
    if ( p_stROLN )
    {
        return p_stROLN -> m_oListOfReachableObjects . fn_bRemoveObject(_p_oObject);
    }
    return FALSE;
}

/*===========================================================================
=========================================================================*/
BOOL CPA_EditorBase::fn_bIsModified(CPA_ReachableObject *_p_oObject, CString _csModificationType /* = "" */)
{
    tdstModifiedObjectsInfo *p_stList = fn_p_stGetMOInfo( _p_oObject -> fn_csGetTypeName() );
    if(p_stList)
    {
        if ( _csModificationType != "" )
        {
            tdstReachableObjectListNamed *p_stROLN = fn_p_stGetROLN (p_stList, _csModificationType );
            return ( p_stROLN ? p_stROLN -> m_oListOfReachableObjects . fn_bExist( _p_oObject ) : FALSE );
        }
        else
        {
            POSITION stPos = p_stList -> m_oListOfModifiedObjects . GetHeadPosition();
            while ( stPos )
            {
                tdstReachableObjectListNamed *p_stROLN = p_stList -> m_oListOfModifiedObjects . GetNext( stPos );
                if( (p_stROLN) && p_stROLN -> m_oListOfReachableObjects . fn_bExist( _p_oObject ) )
                    return TRUE;
            }
        }
    }
    return FALSE;
}

/*===========================================================================
	for dialog list
=========================================================================*/
void CPA_EditorBase::fn_vInitDefaultParameters (CPA_DialogList *pDialog)
{
	pDialog->SetDefaultTestName("No Test");
	pDialog->SetDefaultTypeName("");
}

/*===========================================================================
	save context when editor changes
=========================================================================*/
void CPA_EditorBase::fn_vSaveContext (void)
{
	int i, j;

	// save current dll
    if (GetMainWorld()->GetCurrentEditor() != this)
	{
		if (!m_stContext.pPreviousEditor)
		{
			m_stContext.pPreviousEditor = GetMainWorld()->GetCurrentEditor();
			// save and update current editor
			if (m_stContext.pPreviousEditor)
			{
				m_stContext.bCurrentEditor = m_stContext.pPreviousEditor->fn_bIsCurrentEditor();
				m_stContext.pPreviousEditor->SetCurrent(FALSE);
			}
			// save dialogs
			memcpy(m_stContext.m_aWinArray, g_oFrameGest.ma_p_oWinArray, sizeof(FRMBase *) * (FRM_C_MaxCol+2) * (FRM_C_MaxRow+2));
		}
		// close all editor frames
		for (i=1; i<=FRM_C_MaxCol; i++)
		{
			for (j=1; j<=FRM_C_MaxRow; j++)
			{
				if (((i!=2)||(j!=2))&&(g_oFrameGest.ma_p_oWinArray[i][j]))
					g_oFrameGest.mfn_vDisactivateWindow(g_oFrameGest.ma_p_oWinArray[i][j]);
			}
		}
	}
}


/*===========================================================================
	restore context when editor changes
=========================================================================*/
void CPA_EditorBase::fn_vRestoreContext (void)
{
	int i, j;

	// restore current dll and editor
	if (m_stContext.pPreviousEditor)
	{
		m_stContext.pPreviousEditor->SetCurrent(m_stContext.bCurrentEditor);
		GetMainWorld()->fn_bSetCurrentEditor(m_stContext.pPreviousEditor);
		// close all editor frames
		for (i=1; i<=FRM_C_MaxCol; i++)
		{
			for (j=1; j<=FRM_C_MaxRow; j++)
			{
				if (((i!=2)||(j!=2))&&(g_oFrameGest.ma_p_oWinArray[i][j]))
					g_oFrameGest.mfn_vDisactivateWindow(g_oFrameGest.ma_p_oWinArray[i][j]);
			}
		}
		// restore previous dialogs
		for (i=1; i<=FRM_C_MaxCol; i++)
		{
			for (j=1; j<=FRM_C_MaxRow; j++)
			{
				if (((i!=2)||(j!=2))&&(m_stContext.m_aWinArray[i][j]))
				{
					g_oFrameGest.mfn_vActivateWindow(m_stContext.m_aWinArray[i][j]);
					m_stContext.m_aWinArray[i][j] = NULL;
				}
			}
		}
	}
	// reinit context
	m_stContext.pPreviousEditor = NULL;
}



/*===========================================================================
=========================================================================*/
CString CPA_EditorBase::GetReferencePath (CString csEntry)
{
	CString	csFileName, csReferencePath;
	char	szPath[SCR_CV_ui_Cfg_MaxLenName];

	// search ini file for given entry
	csFileName = M_GetMainApp()->m_csEditorDataPath + C_szReferenceIniFile;
	GetPrivateProfileString ("ReferencePath", (char*)(LPCSTR)csEntry, "", szPath, 256, (char*)(LPCSTR)csFileName);
	// build reference path
	csReferencePath = szPath;
	return csReferencePath;

}


	
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// private
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////

/*===========================================================================
=========================================================================*/
BOOL CPA_EditorBase::fn_bIsRegisterModificationType(CString _csObjectType, CString _csModificationType)
{
    tdstModifiedObjectsInfo *p_stList = fn_p_stGetMOInfo(_csObjectType);

    return (p_stList ? fn_bIsRegisterModificationType(p_stList, _csModificationType) : FALSE );
}

/*===========================================================================
=========================================================================*/
BOOL CPA_EditorBase::fn_bIsRegisterModificationType(tdstModifiedObjectsInfo *p_stMOI, CString _csModificationType)
{
    POSITION stPos = p_stMOI -> m_oListOfModifiedObjects . GetHeadPosition();
    while ( stPos )
    {
        if( p_stMOI -> m_oListOfModifiedObjects . GetNext( stPos ) -> m_ocsModificationTypeName == _csModificationType )
            return TRUE;
    }
    return FALSE;
}

/*===========================================================================
=========================================================================*/
tdstModifiedObjectsInfo *CPA_EditorBase::fn_p_stGetMOInfo(CString _csObjectType)
{
    POSITION stPos = m_oListOfModifiedReachableObjects . GetHeadPosition();
    while ( stPos )
    {
        tdstModifiedObjectsInfo *p_stList = m_oListOfModifiedReachableObjects . GetNext( stPos );
        if( p_stList -> m_oObjectType == _csObjectType )
            return p_stList;
    }
    return NULL;
}

/*===========================================================================
=========================================================================*/
tdstReachableObjectListNamed *CPA_EditorBase::fn_p_stGetROLN(CString _csObjectType, CString _csModificationType)
{
    tdstModifiedObjectsInfo *p_stList = fn_p_stGetMOInfo(_csObjectType);

    return (p_stList ? fn_p_stGetROLN(p_stList, _csModificationType) : NULL );

}

/*===========================================================================
=========================================================================*/
tdstReachableObjectListNamed *CPA_EditorBase::fn_p_stGetROLN(tdstModifiedObjectsInfo *_p_stMOI, CString _csModificationType)
{
    POSITION stPos = _p_stMOI -> m_oListOfModifiedObjects . GetHeadPosition();
    while ( stPos )
    {
        tdstReachableObjectListNamed *p_stList = _p_stMOI -> m_oListOfModifiedObjects . GetNext( stPos );
        if( p_stList -> m_ocsModificationTypeName == _csModificationType )
            return p_stList;
    }
    return NULL;
}

#endif // ACTIVE_EDITOR
