471 lines
18 KiB
C++
471 lines
18 KiB
C++
// diamain.cpp : implementation file
|
|
//
|
|
|
|
#include "stdafx.h"
|
|
#include "Acp_Base.h"
|
|
|
|
#define HieFriend
|
|
|
|
#include "inctex.h"
|
|
#include "_interf.hpp"
|
|
#include "diamain.hpp"
|
|
#include "matlist.hpp"
|
|
#include "GMatObj.hpp"
|
|
#include "x:\cpa\main\inc\_editid.h"
|
|
#include "TUT.h"
|
|
|
|
#include "DlgPreloadGMT.h"
|
|
|
|
extern Material_Interface *gs_p_oMaterialInterface;
|
|
|
|
#define C_szPurgeMarker "(P)"
|
|
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
// DiaMain dialog
|
|
|
|
IMPLEMENT_DYNCREATE(DiaMain, CFormView)
|
|
|
|
DiaMain::DiaMain()
|
|
: CFormView(DiaMain::IDD)
|
|
{
|
|
m_wLastSelectedFileIndex = CB_ERR;
|
|
//{{AFX_DATA_INIT(DiaMain)
|
|
// NOTE: the ClassWizard will add member initialization here
|
|
//}}AFX_DATA_INIT
|
|
}
|
|
|
|
|
|
void DiaMain::DoDataExchange(CDataExchange* pDX)
|
|
{
|
|
CFormView::DoDataExchange(pDX);
|
|
//{{AFX_DATA_MAP(DiaMain)
|
|
// NOTE: the ClassWizard will add DDX and DDV calls here
|
|
//}}AFX_DATA_MAP
|
|
}
|
|
|
|
|
|
BEGIN_MESSAGE_MAP(DiaMain, CFormView)
|
|
//{{AFX_MSG_MAP(DiaMain)
|
|
ON_BN_CLICKED(IDC_BUTTON_NEW, OnButtonNew)
|
|
ON_BN_CLICKED(IDC_BUTTON_COPY, OnButtonCopy)
|
|
ON_BN_CLICKED(IDC_BUTTON_DELETE, OnButtonDelete)
|
|
ON_BN_CLICKED(IDC_BUTTON_LOADGMT, OnButtonLoadGMT)
|
|
ON_BN_CLICKED(IDC_BUTTON_UNLOADGMT, OnButtonUnLoadGMT)
|
|
ON_EN_CHANGE(IDC_EDIT_NAME, OnChangeEditName)
|
|
ON_CBN_SELCHANGE(IDC_COMBO_FILE, OnComboFileSelChange)
|
|
ON_BN_CLICKED(IDC_BUTTON_PURGE, OnButtonPurge)
|
|
//}}AFX_MSG_MAP
|
|
END_MESSAGE_MAP()
|
|
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
// DiaMain message handlers
|
|
|
|
//==================================================================================================
|
|
//==================================================================================================
|
|
int DiaMain::OnToolHitTest(CPoint point, TOOLINFO* pTI) const
|
|
{
|
|
CString csMessage;
|
|
switch ( CWnd::OnToolHitTest(point, pTI) )
|
|
{
|
|
case IDC_COMBO_FILE:
|
|
csMessage = "select the file for wich you can view/edit materials";
|
|
break;
|
|
|
|
case IDC_BUTTON_NEW:
|
|
csMessage = "create a new material with the specified name";
|
|
break;
|
|
|
|
case IDC_BUTTON_COPY:
|
|
csMessage = "create a copy of the current game material that will use the same characteristics as the original";
|
|
break;
|
|
|
|
case IDC_EDIT_NAME:
|
|
csMessage = "view the current game material's name";
|
|
break;
|
|
|
|
case IDC_BUTTON_DELETE:
|
|
csMessage = "delete the current game material";
|
|
break;
|
|
|
|
case IDC_BUTTON_PURGE:
|
|
csMessage = "cause the deletion of all unused texture sections for the materials belonging to this file";
|
|
break;
|
|
}
|
|
//output the help info in the status bar
|
|
fn_vGiveProgressInfo(csMessage, -1);
|
|
|
|
//there is no hit for a true tooltip
|
|
return -1;
|
|
}
|
|
|
|
/*=========================================================================
|
|
Function Name: DiaCol::m_fn_vShowMaterial
|
|
Purpose: Show the collision paramaters of the current material
|
|
Author: Yann Le Tensorer
|
|
Date: 23-04-97
|
|
|
|
This function is called each time a material is selected.
|
|
==========================================================================*/
|
|
void DiaMain::m_fn_vShowMaterial(GMT_tdxHandleToGameMaterial /*_hMat*/)
|
|
{
|
|
//ANNECY Shaitan DisableFunctions (16/04/98) {
|
|
char *szOwnerName;
|
|
char *szFileName;
|
|
//ENDANNECY Shaitan DisableFunctions }
|
|
|
|
tdoEditorGameMaterial *p_oCurrentEditedGameMaterial = gs_p_oMaterialInterface->m_p_oGetEditedMaterial();
|
|
BOOL bAGameMaterialIsEdited = p_oCurrentEditedGameMaterial ? TRUE : FALSE;
|
|
|
|
//ANNECY Shaitan DisableFunctions (16/04/98) {
|
|
if (bAGameMaterialIsEdited)
|
|
{
|
|
szOwnerName = (char *) (LPCSTR) p_oCurrentEditedGameMaterial->GetOwner()->GetName();
|
|
szFileName = strchr(szOwnerName, '\\') + 1;
|
|
m_bCommonFile = (!stricmp(szFileName, C_szCommon));
|
|
}
|
|
//ENDANNECY Shaitan DisableFunctions }
|
|
|
|
if ( bAGameMaterialIsEdited )
|
|
{
|
|
//ANNECY Shaitan DisableFunctions (16/04/98) {
|
|
GetDlgItem(IDC_BUTTON_COPY)->EnableWindow(/*TRUE*/!m_bCommonFile || g_bMasterEditor);
|
|
GetDlgItem(IDC_BUTTON_DELETE)->EnableWindow(/*TRUE*/!m_bCommonFile || g_bMasterEditor);
|
|
//ENDANNECY Shaitan DisableFunctions }
|
|
GetDlgItem(IDC_EDIT_NAME)->SetWindowText(p_oCurrentEditedGameMaterial->GetName());
|
|
}
|
|
else
|
|
{
|
|
GetDlgItem(IDC_BUTTON_COPY)->EnableWindow(FALSE);
|
|
GetDlgItem(IDC_BUTTON_DELETE)->EnableWindow(FALSE);
|
|
GetDlgItem(IDC_EDIT_NAME)->SetWindowText("");
|
|
}
|
|
GetDlgItem(IDC_EDIT_NAME)->Invalidate();
|
|
//GetDlgItem(IDC_COMBO_FILE)->EnableWindow(bAGameMaterialIsEdited);
|
|
}
|
|
|
|
//=================================================================================
|
|
//=================================================================================
|
|
CPA_FileObject *DiaMain::m_p_oGetCurrentFileObject()
|
|
{
|
|
return (CPA_FileObject *) (
|
|
(m_wLastSelectedFileIndex != CB_ERR)
|
|
? ((CComboBox *) GetDlgItem(IDC_COMBO_FILE))->GetItemDataPtr(m_wLastSelectedFileIndex)
|
|
: NULL
|
|
);
|
|
}
|
|
|
|
//=================================================================================
|
|
//=================================================================================
|
|
void DiaMain::m_vRemoveFile(CPA_FileObject *_p_oFileObject)
|
|
{
|
|
CComboBox *p_oCombo = ((CComboBox *) GetDlgItem(IDC_COMBO_FILE));
|
|
for (short wIndex = 0; wIndex < p_oCombo->GetCount(); wIndex ++ )
|
|
if ( p_oCombo->GetItemDataPtr(wIndex) == _p_oFileObject )
|
|
{
|
|
p_oCombo->DeleteString(wIndex);
|
|
}
|
|
m_wLastSelectedFileIndex = (short) p_oCombo->GetCurSel();
|
|
}
|
|
|
|
//=================================================================================
|
|
//=================================================================================
|
|
void DiaMain::m_vAddFile(CPA_FileObject *_p_oFileObject)
|
|
{
|
|
CComboBox *p_oCombo = ((CComboBox *) GetDlgItem(IDC_COMBO_FILE));
|
|
CString csfileName = _p_oFileObject->GetReferencedName(gs_p_oMaterialInterface, C_szFileMinimalNameKey);
|
|
short wIndex = (short) p_oCombo->FindStringExact(CB_ERR, csfileName);
|
|
//do not add the file twice
|
|
if ( wIndex != CB_ERR && p_oCombo->GetItemDataPtr(wIndex) == _p_oFileObject )
|
|
return;
|
|
|
|
//add the string and a pointer to the object
|
|
wIndex = (short) p_oCombo->AddString(csfileName);
|
|
p_oCombo->SetItemDataPtr(wIndex, _p_oFileObject);
|
|
|
|
m_wLastSelectedFileIndex = (short) p_oCombo->GetCurSel();
|
|
}
|
|
|
|
//=================================================================================
|
|
//=================================================================================
|
|
void DiaMain::OnComboFileSelChange()
|
|
{
|
|
m_vReflectFileSelectionChange();
|
|
}
|
|
|
|
//=================================================================================
|
|
//=================================================================================
|
|
void DiaMain::m_vReflectFileSelectionChange()
|
|
{
|
|
short wNewIndex = (short) (((CComboBox *) GetDlgItem(IDC_COMBO_FILE))->GetCurSel());
|
|
if ( wNewIndex != m_wLastSelectedFileIndex )
|
|
{
|
|
m_wLastSelectedFileIndex = wNewIndex;
|
|
//make all the editor undisplay the current material
|
|
gs_p_oMaterialInterface->m_p_oGetMaterialListView()->OnSelectItem(TSL_cItemNotFound);
|
|
//make the listview display the materials currently used by the engine
|
|
gs_p_oMaterialInterface->m_p_oGetMaterialListView()->m_vRefreshDisplayedMaterialList(
|
|
gs_p_oMaterialInterface->m_p_oGetMaterialListView()->m_bAllMaterialsDisplayed(),
|
|
TRUE //since the owner file changed, remove all currently displayed materials
|
|
);
|
|
//now lets see if we enable the purge button
|
|
m_vUpdatePurgeStatus(wNewIndex);
|
|
}
|
|
//ANNECY Shaitan DisableFunctions (16/04/98) {
|
|
if (wNewIndex != CB_ERR)
|
|
{
|
|
char szText[256];
|
|
((CComboBox *) GetDlgItem(IDC_COMBO_FILE))->GetLBText(wNewIndex, szText);
|
|
m_bCommonFile = !stricmp(szText, "Common.gmt");
|
|
}
|
|
GetDlgItem(IDC_BUTTON_NEW)->EnableWindow((wNewIndex != CB_ERR) && (!m_bCommonFile || g_bMasterEditor));
|
|
//ENDANNECY Shaitan DisableFunctions }
|
|
}
|
|
|
|
//=================================================================================
|
|
//=================================================================================
|
|
void DiaMain::m_vUpdatePurgeStatus(short _wFileIndex /*= CB_ERR*/)
|
|
{
|
|
if ( _wFileIndex == CB_ERR )
|
|
_wFileIndex = (short) (((CComboBox *) GetDlgItem(IDC_COMBO_FILE))->GetCurSel());
|
|
BOOL bEnablePurge = TRUE;
|
|
CPA_FileObject *p_oSelectedFileObject = (CPA_FileObject *) (((CComboBox *) GetDlgItem(IDC_COMBO_FILE))->GetItemDataPtr(_wFileIndex));
|
|
CList<CPA_BaseObject *, CPA_BaseObject *> oChildList;
|
|
int nbChilds = g_oCoherenceManager.m_fn_iGetChildList(p_oSelectedFileObject, &oChildList);
|
|
POSITION xPos = oChildList.GetHeadPosition();
|
|
while ( xPos )
|
|
{
|
|
CPA_BaseObject *p_oChild = oChildList.GetNext(xPos);
|
|
if ( !p_oChild->fn_bIsAvailable() ) //the object is created for the section, but the section was not analysed...
|
|
{
|
|
bEnablePurge = FALSE;
|
|
break;
|
|
}
|
|
}
|
|
GetDlgItem(IDC_BUTTON_PURGE)->EnableWindow(bEnablePurge);
|
|
}
|
|
|
|
//==================================================================================================
|
|
// Description : handle function for BN_CLICKED an IDC_BUTTON_NEW
|
|
//==================================================================================================
|
|
void DiaMain::OnButtonNew()
|
|
{
|
|
// ask to MatList to create a new material
|
|
CString csMaterialName;
|
|
GetDlgItem(IDC_EDIT_NAME)->GetWindowText(csMaterialName);
|
|
//if it is empty, default to the automatic name
|
|
if ( csMaterialName.IsEmpty() )
|
|
csMaterialName = C_szGameMaterialTypeName "1";
|
|
//then get the prefixed version
|
|
csMaterialName = gs_p_oMaterialInterface->GetInterface()->GetPrefixedName(csMaterialName);
|
|
|
|
// ask to MatList to create a new material
|
|
gs_p_oMaterialInterface->m_p_oGetMaterialListView()->m_fn_vCreateNewMaterial(NULL, csMaterialName);
|
|
}
|
|
|
|
//==================================================================================================
|
|
//==================================================================================================
|
|
void DiaMain::OnButtonCopy()
|
|
{
|
|
//get the name for the new material
|
|
CString csNewMaterialName;
|
|
GetDlgItem(IDC_EDIT_NAME)->GetWindowText(csNewMaterialName);
|
|
//if it is not empty, get the prefixed version
|
|
if ( !csNewMaterialName.IsEmpty() )
|
|
csNewMaterialName = gs_p_oMaterialInterface->GetInterface()->GetPrefixedName(csNewMaterialName);
|
|
|
|
// ask to MatList to create a new material copied from the current one
|
|
gs_p_oMaterialInterface->m_p_oGetMaterialListView()->m_fn_vCreateNewMaterial(
|
|
gs_p_oMaterialInterface->m_p_oGetEditedMaterial(),
|
|
csNewMaterialName
|
|
);
|
|
}
|
|
|
|
//==================================================================================================
|
|
//==================================================================================================
|
|
void DiaMain::OnButtonDelete()
|
|
{
|
|
gs_p_oMaterialInterface->m_p_oGetMaterialListView()->m_fn_vDeleteMaterial(gs_p_oMaterialInterface->m_p_oGetEditedMaterial());
|
|
}
|
|
|
|
//==================================================================================================
|
|
//==================================================================================================
|
|
void DiaMain::OnChangeEditName()
|
|
{
|
|
CString csMaterialName;
|
|
GetDlgItem(IDC_EDIT_NAME)->GetWindowText(csMaterialName);
|
|
tdoEditorGameMaterial *p_oGameMaterial = NULL;
|
|
if ( !csMaterialName.IsEmpty() )
|
|
{
|
|
csMaterialName = gs_p_oMaterialInterface->GetInterface()->GetPrefixedName(csMaterialName);
|
|
//if we did not find a material with the engine, try to find one with the name, in case the script was
|
|
//parsed and editor materials were created for unloaded sections -> they have no valid engine material
|
|
if ( !csMaterialName.IsEmpty() )
|
|
p_oGameMaterial = (tdoEditorGameMaterial *) gs_p_oMaterialInterface->GetMainWorld()->fn_p_oFindObject(
|
|
csMaterialName, C_szGameMaterialTypeName, gs_p_oMaterialInterface->m_p_oGetCurrentFileObject()
|
|
);
|
|
}
|
|
//ANNECY Shaitan DisableFunctions (16/04/98) {
|
|
//if the name is unique for all materials, enable the creation of a material
|
|
GetDlgItem(IDC_BUTTON_NEW)->EnableWindow(!p_oGameMaterial && (!m_bCommonFile || g_bMasterEditor));
|
|
//to enable a copy, the name must be unique, and a current material must be selected
|
|
GetDlgItem(IDC_BUTTON_COPY)->EnableWindow((!p_oGameMaterial) && gs_p_oMaterialInterface->m_p_oGetEditedMaterial() && (!m_bCommonFile || g_bMasterEditor));
|
|
//ENDANNECY Shaitan DisableFunctions }
|
|
}
|
|
|
|
//==================================================================================================
|
|
//==================================================================================================
|
|
void DiaMain::m_vSelectFileOfMaterial(tdoEditorGameMaterial *_p_oGameMaterial)
|
|
{
|
|
CComboBox *p_oCombo = ((CComboBox *) GetDlgItem(IDC_COMBO_FILE));
|
|
for (short wIndex = 0; wIndex < p_oCombo->GetCount(); wIndex ++ )
|
|
if ( p_oCombo->GetItemDataPtr(wIndex) == _p_oGameMaterial->GetOwner() )
|
|
{
|
|
p_oCombo->SetCurSel(wIndex);
|
|
OnComboFileSelChange();
|
|
}
|
|
}
|
|
|
|
//==================================================================================================
|
|
//==================================================================================================
|
|
void DiaMain::OnButtonPurge()
|
|
{
|
|
//if all game materials of this file have been loaded, the purge will enable the deletion of
|
|
//all the sections belonging to unreferenced texture objects
|
|
//mark this by adding a (P) in the string
|
|
short wIndex = (short) (((CComboBox *) GetDlgItem(IDC_COMBO_FILE))->GetCurSel());
|
|
if ( wIndex != CB_ERR )
|
|
{
|
|
CPA_FileObject *p_oSelectedFileObject = (CPA_FileObject *) (((CComboBox *) GetDlgItem(IDC_COMBO_FILE))->GetItemDataPtr(wIndex));
|
|
CString csComboString;
|
|
((CComboBox *) GetDlgItem(IDC_COMBO_FILE))->GetLBText(wIndex, csComboString);
|
|
if ( csComboString.Find(C_szPurgeMarker) == -1 ) //if the string is not marked for purge
|
|
{
|
|
//add the mark
|
|
csComboString += " " C_szPurgeMarker;
|
|
//and replace the string
|
|
((CComboBox *) GetDlgItem(IDC_COMBO_FILE))->DeleteString(wIndex);
|
|
((CComboBox *) GetDlgItem(IDC_COMBO_FILE))->InsertString(wIndex, csComboString);
|
|
((CComboBox *) GetDlgItem(IDC_COMBO_FILE))->SetItemDataPtr(wIndex, p_oSelectedFileObject);
|
|
((CComboBox *) GetDlgItem(IDC_COMBO_FILE))->SetCurSel(wIndex);
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
//==================================================================================================
|
|
//==================================================================================================
|
|
void DiaMain::m_vGetListOfFilesToPurge(CPA_List<CPA_FileObject> *p_oListToFill)
|
|
{
|
|
p_oListToFill->RemoveAll();
|
|
short wIndex, wMax = ((CComboBox *) GetDlgItem(IDC_COMBO_FILE))->GetCount();
|
|
for ( wIndex = 0; wIndex < wMax; wIndex ++ )
|
|
{
|
|
CString csComboString;
|
|
((CComboBox *) GetDlgItem(IDC_COMBO_FILE))->GetLBText(wIndex, csComboString);
|
|
if ( csComboString.Find(C_szPurgeMarker) != -1 )
|
|
p_oListToFill->AddTail((CPA_FileObject *) ((CComboBox *) GetDlgItem(IDC_COMBO_FILE))->GetItemDataPtr(wIndex));
|
|
}
|
|
}
|
|
|
|
//==================================================================================================
|
|
//==================================================================================================
|
|
BOOL DiaMain::Create(LPCTSTR lpszClassName, LPCTSTR lpszWindowName, DWORD dwStyle, const RECT& rect, CWnd* pParentWnd, UINT nID, CCreateContext* pContext)
|
|
{
|
|
// TODO: Add your specialized code here and/or call the base class
|
|
if ( CFormView::Create(lpszClassName, lpszWindowName, dwStyle, rect, pParentWnd, nID, pContext) )
|
|
{
|
|
EnableToolTips(TRUE);
|
|
|
|
//control registration for tutorial and assistants, should be unregistered when view is destroyed
|
|
TUT_M_vGetTutDll();
|
|
TUT_M_vRegisterControlID(IDC_COMBO_FILE,"TGM_DiaMainComboFiles",TUT_e_ComboBox);
|
|
TUT_M_vRegisterControlID(IDC_BUTTON_PURGE,"TGM_DiaMainButtonPurge",TUT_e_Button);
|
|
TUT_M_vRegisterControlID(IDC_EDIT_NAME,"TGM_DiaMainEditName",TUT_e_TextEdit);
|
|
TUT_M_vRegisterControlID(IDC_BUTTON_NEW,"TGM_DiaMainbttonNew",TUT_e_Button);
|
|
TUT_M_vRegisterControlID(IDC_BUTTON_COPY,"TGM_DiaMainButtonCopy",TUT_e_Button);
|
|
TUT_M_vRegisterControlID(IDC_BUTTON_DELETE,"TGM_DiaMainButtonDelete",TUT_e_Button);
|
|
|
|
return TRUE;
|
|
}
|
|
else
|
|
return FALSE;
|
|
}
|
|
|
|
// Christophe Modifications {
|
|
static long gs_lNbPreviousMaterialsInLinkTable = 0;
|
|
static long gs_lNbPreviousGeomobjInLinkTable = 0;
|
|
|
|
|
|
// ***** Add a PreLoad GMT in Material.ini *****
|
|
void DiaMain::OnButtonLoadGMT()
|
|
{
|
|
CFileDialog cfFileDial(TRUE,"gmt");
|
|
|
|
char szBackupPath[255];
|
|
GetCurrentDirectory(255,szBackupPath);
|
|
|
|
if ( cfFileDial.DoModal() == IDOK )
|
|
{
|
|
char szDrive[_MAX_DRIVE],
|
|
szPath[_MAX_DIR],
|
|
szName[_MAX_FNAME],
|
|
szExt[_MAX_EXT],
|
|
sz255GMTFileName[255],
|
|
sz255Tmp[255],
|
|
sz8SubSectionName[8],
|
|
sz3FileNumber[3];
|
|
|
|
unsigned char FileNumber = 1;
|
|
|
|
CString csFileName = cfFileDial.GetPathName();
|
|
UpdateData(FALSE);
|
|
|
|
//insert a subdirectory in the name of the image to compute the full name of the small file
|
|
_splitpath(LPCTSTR(csFileName), szDrive, szPath, szName, szExt);
|
|
strcpy(sz255GMTFileName, szName);
|
|
strcat(sz255GMTFileName, "\\");
|
|
strcat(sz255GMTFileName, szName);
|
|
strcat(sz255GMTFileName, szExt);
|
|
|
|
CString csMaterialFileName = M_GetMainApp()->m_csEditorDataPath + "Tools\\Material\\Material.ini";
|
|
|
|
// Search end of subsection to insert the new one
|
|
do
|
|
{
|
|
strcpy(sz8SubSectionName, "File");
|
|
strcat(sz8SubSectionName, itoa(FileNumber++, sz3FileNumber, 10) );
|
|
GetPrivateProfileString("Preload", sz8SubSectionName, "", sz255Tmp, 255, LPCTSTR(csMaterialFileName));
|
|
} while(sz255Tmp[0]);
|
|
|
|
// Write the new subsection
|
|
WritePrivateProfileString("Preload", sz8SubSectionName, sz255GMTFileName, LPCTSTR(csMaterialFileName));
|
|
|
|
// Restore current directory
|
|
SetCurrentDirectory(szBackupPath);
|
|
SCR_fnp_st_RdL0_AnalyseSection(sz255GMTFileName, SCR_CDF_uw_Anl_Normal);
|
|
|
|
//we want to rescan all linktables from the beginning
|
|
gs_lNbPreviousMaterialsInLinkTable = 0;
|
|
gs_lNbPreviousGeomobjInLinkTable = 0;
|
|
//all existent editor materials must be destroyed before the new ones are linked to the engine materials
|
|
gs_p_oMaterialInterface->m_vDoRefreshLists(0, E_rlm_UpdateWithoutDestruction, TRUE/*FALSE*/);
|
|
//read the highlight information from the ini file.
|
|
gs_p_oMaterialInterface->m_vLoadHighlightInfo();
|
|
}
|
|
|
|
}
|
|
|
|
// ***** Remove a PreLoad GMT in Material.ini *****
|
|
void DiaMain::OnButtonUnLoadGMT()
|
|
{
|
|
CDlgPreloadGMT oDlgPreLoad;
|
|
|
|
CString csUnloadedGMT;
|
|
|
|
if (oDlgPreLoad.DoModal() == IDOK)
|
|
{
|
|
csUnloadedGMT = oDlgPreLoad.m_szGetSelectedText();
|
|
}
|
|
}
|
|
// } End Christophe modifications
|