1280 lines
37 KiB
C++
1280 lines
37 KiB
C++
/////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// Copyright (c) 1996-2000 Microsoft Corporation
|
|
//
|
|
// Module Name:
|
|
// AtlLCPair.h
|
|
//
|
|
// Implementation File:
|
|
// None.
|
|
//
|
|
// Description:
|
|
// Definition of the CListCtrlPair dialog.
|
|
// Derive from CDialogImpl<> or CPropertyPageImpl<>.
|
|
//
|
|
// Author:
|
|
// David Potter (davidp) August 8, 1996
|
|
//
|
|
// Revision History:
|
|
//
|
|
// Notes:
|
|
//
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
|
|
#ifndef __ATLLCPAIR_H_
|
|
#define __ATLLCPAIR_H_
|
|
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
// Forward Class Declarations
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
|
|
template < class T, class ObjT, class BaseT > class CListCtrlPair;
|
|
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
// External Class Declarations
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
// Include Files
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
|
|
#ifndef __ADMCOMMONRES_H_
|
|
#include "AdmCommonRes.h" // for ADMC_IDC_LCP_xxx
|
|
#endif
|
|
|
|
#ifndef __ATLUTIL_H_
|
|
#include "AtlUtil.h" // for DDX_xxx
|
|
#endif
|
|
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
// Type Definitions
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
|
|
struct CLcpColumn
|
|
{
|
|
UINT m_idsText;
|
|
int m_nWidth;
|
|
};
|
|
|
|
#define LCPS_SHOW_IMAGES 0x1
|
|
#define LCPS_ALLOW_EMPTY 0x2
|
|
#define LCPS_CAN_BE_ORDERED 0x4
|
|
#define LCPS_ORDERED 0x8
|
|
#define LCPS_DONT_OUTPUT_RIGHT_LIST 0x10
|
|
#define LCPS_READ_ONLY 0x20
|
|
#define LCPS_PROPERTIES_BUTTON 0x40
|
|
#define LCPS_MAX 0x40
|
|
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
//++
|
|
//
|
|
// class CListCtrlPair
|
|
//
|
|
// Description:
|
|
// Class to support dual list box.
|
|
//
|
|
// Inheritance:
|
|
// CListCtrlPair< T, ObjT, BaseT >
|
|
// <BaseT>
|
|
// ...
|
|
// CDialogImpl< T >
|
|
//
|
|
//--
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
|
|
template < class T, class ObjT, class BaseT >
|
|
class CListCtrlPair : public BaseT
|
|
{
|
|
// friend class CListCtrlPairDlg;
|
|
// friend class CListCtrlPairPage;
|
|
// friend class CListCtrlPairWizPage;
|
|
|
|
typedef CListCtrlPair< T, ObjT, BaseT > thisClass;
|
|
typedef std::list< ObjT * > _objptrlist;
|
|
typedef std::list< ObjT * >::iterator _objptrlistit;
|
|
|
|
protected:
|
|
// Column structure and collection.
|
|
typedef std::vector< CLcpColumn > CColumnArray;
|
|
CColumnArray m_aColumns;
|
|
|
|
// Sort information.
|
|
struct SortInfo
|
|
{
|
|
int m_nDirection;
|
|
int m_nColumn;
|
|
};
|
|
|
|
public:
|
|
//
|
|
// Construction
|
|
//
|
|
|
|
// Default constructor
|
|
CListCtrlPair( void )
|
|
{
|
|
CommonConstruct();
|
|
|
|
} //*** CListCtrlPair()
|
|
|
|
// Constructor with style specified
|
|
CListCtrlPair(
|
|
IN DWORD dwStyle,
|
|
IN LPCTSTR lpszTitle = NULL
|
|
)
|
|
: BaseT( lpszTitle )
|
|
{
|
|
CommonConstruct();
|
|
m_dwStyle = dwStyle;
|
|
|
|
} //*** CListCtrlPair( lpszTitle )
|
|
|
|
// Constructor with style specified
|
|
CListCtrlPair(
|
|
IN DWORD dwStyle,
|
|
IN UINT nIDTitle
|
|
)
|
|
: BaseT( nIDTitle )
|
|
{
|
|
CommonConstruct();
|
|
m_dwStyle = dwStyle;
|
|
|
|
} //*** CListCtrlPair( nIDTitle )
|
|
|
|
// Common object construction
|
|
void CommonConstruct( void )
|
|
{
|
|
m_dwStyle = LCPS_ALLOW_EMPTY;
|
|
m_plvcFocusList = NULL;
|
|
|
|
// Set the sort info.
|
|
SiLeft().m_nDirection = -1;
|
|
SiLeft().m_nColumn = -1;
|
|
SiRight().m_nDirection = -1;
|
|
SiRight().m_nColumn = -1;
|
|
|
|
} //*** CommonConstruct()
|
|
|
|
public:
|
|
//
|
|
// Functions that are required to be implemented by derived class.
|
|
//
|
|
|
|
// Return list of objects for right list control
|
|
_objptrlist * PlpobjRight( void ) const
|
|
{
|
|
ATLTRACE( _T("PlpobjRight() - Define in derived class\n") );
|
|
ATLASSERT( 0 );
|
|
return NULL;
|
|
|
|
} //*** PlpobjRight()
|
|
|
|
// Return list of objects for left list control
|
|
const _objptrlist * PlpobjLeft( void ) const
|
|
{
|
|
ATLTRACE( _T("PlpobjLeft() - Define in derived class\n") );
|
|
ATLASSERT( 0 );
|
|
return NULL;
|
|
|
|
} //*** PlpobjLeft()
|
|
|
|
// Get column text and image
|
|
void GetColumnInfo(
|
|
IN OUT ObjT * pobj,
|
|
IN int iItem,
|
|
IN int icol,
|
|
OUT CString & rstr,
|
|
OUT int * piimg
|
|
)
|
|
{
|
|
ATLTRACE( _T("GetColumnInfo() - Define in derived class\n") );
|
|
ATLASSERT( 0 );
|
|
|
|
} //*** GetColumnInfo()
|
|
|
|
// Display properties for the object
|
|
int BDisplayProperties( IN OUT ObjT * pobj )
|
|
{
|
|
ATLTRACE( _T("BDisplayProperties() - Define in derived class\n") );
|
|
ATLASSERT( 0 );
|
|
return FALSE;
|
|
|
|
} //*** BDisplayProperties()
|
|
|
|
// Display an application-wide message box
|
|
virtual int AppMessageBox( LPCWSTR lpszText, UINT fuStyle )
|
|
{
|
|
ATLTRACE( _T("BDisplayProperties() - Define in derived class\n") );
|
|
ATLASSERT( 0 );
|
|
return MessageBox( lpszText, _T(""), fuStyle );
|
|
|
|
} //*** AppMessageBox()
|
|
|
|
// Display an application-wide message box
|
|
int AppMessageBox( UINT nID, UINT fuStyle )
|
|
{
|
|
CString strMsg;
|
|
strMsg.LoadString( nID );
|
|
return AppMessageBox( strMsg, fuStyle );
|
|
|
|
} //*** AppMessageBox()
|
|
|
|
protected:
|
|
//
|
|
// List control pair style.
|
|
//
|
|
|
|
DWORD m_dwStyle;
|
|
|
|
BOOL BIsStyleSet( IN DWORD dwStyle ) const { return (m_dwStyle & dwStyle) == dwStyle; }
|
|
void ModifyStyle( IN DWORD dwRemove, IN DWORD dwAdd )
|
|
{
|
|
ATLASSERT( (dwRemove & dwAdd) == 0 );
|
|
if ( dwRemove != 0 )
|
|
{
|
|
m_dwStyle &= ~dwRemove;
|
|
} // if: removing some styles
|
|
if ( dwAdd != 0 )
|
|
{
|
|
m_dwStyle |= dwAdd;
|
|
} // if: adding some styles
|
|
|
|
} //*** ModifyStyle()
|
|
|
|
DWORD DwStyle( void ) const { return m_dwStyle; }
|
|
BOOL BShowImages( void ) const { return BIsStyleSet( LCPS_SHOW_IMAGES ); }
|
|
BOOL BAllowEmpty( void ) const { return BIsStyleSet( LCPS_ALLOW_EMPTY ); }
|
|
BOOL BCanBeOrdered( void ) const { return BIsStyleSet( LCPS_CAN_BE_ORDERED ); }
|
|
BOOL BOrdered( void ) const { return BIsStyleSet( LCPS_ORDERED ); }
|
|
BOOL BReadOnly( void ) const { return BIsStyleSet( LCPS_READ_ONLY ); }
|
|
BOOL BPropertiesButton( void ) const { return BIsStyleSet( LCPS_PROPERTIES_BUTTON ); }
|
|
|
|
// Operations
|
|
public:
|
|
|
|
// Add column to list of columns displayed in each list control
|
|
void AddColumn( IN UINT idsText, IN int nWidth )
|
|
{
|
|
CLcpColumn col;
|
|
|
|
ATLASSERT( idsText != 0 );
|
|
ATLASSERT( nWidth > 0 );
|
|
ATLASSERT( LpobjRight().empty() );
|
|
|
|
col.m_idsText = idsText;
|
|
col.m_nWidth = nWidth;
|
|
|
|
m_aColumns.insert( m_aColumns.end(), col );
|
|
|
|
} //*** AddColumn()
|
|
|
|
// Insert an item in a list control
|
|
int NInsertItemInListCtrl(
|
|
IN int iitem,
|
|
IN OUT ObjT * pobj,
|
|
IN OUT CListViewCtrl & rlc
|
|
)
|
|
{
|
|
int iRetItem;
|
|
CString strText;
|
|
int iimg = 0;
|
|
int icol;
|
|
|
|
// Insert the first column.
|
|
((T *) this)->GetColumnInfo( pobj, iitem, 0, strText, &iimg );
|
|
iRetItem = rlc.InsertItem(
|
|
LVIF_TEXT | LVIF_IMAGE | LVIF_PARAM, // nMask
|
|
iitem, // nItem
|
|
strText, // lpszItem
|
|
0, // nState
|
|
0, // nStateMask
|
|
iimg, // nImage
|
|
(LPARAM) pobj // lParam
|
|
);
|
|
ATLASSERT( iRetItem != -1 );
|
|
|
|
for ( icol = 1 ; icol < m_aColumns.size() ; icol++ )
|
|
{
|
|
((T *) this)->GetColumnInfo( pobj, iRetItem, icol, strText, NULL );
|
|
rlc.SetItemText( iRetItem, icol, strText );
|
|
} // for: each column
|
|
|
|
return iRetItem;
|
|
|
|
} //*** NInsertItemInListCtrl()
|
|
|
|
// Update data on or from the dialog
|
|
BOOL UpdateData( IN BOOL bSaveAndValidate )
|
|
{
|
|
BOOL bSuccess = TRUE;
|
|
|
|
if ( bSaveAndValidate )
|
|
{
|
|
//
|
|
// Verify that the list is not empty.
|
|
//
|
|
if ( ! BAllowEmpty() && (m_lvcRight.GetItemCount() == 0) )
|
|
{
|
|
CString strMsg;
|
|
CString strLabel;
|
|
TCHAR * pszLabel;
|
|
|
|
DDX_GetText( m_hWnd, ADMC_IDC_LCP_RIGHT_LABEL, strLabel );
|
|
|
|
//
|
|
// Remove ampersands (&) and colons (:).
|
|
//
|
|
pszLabel = strLabel.GetBuffer( 1 );
|
|
CleanupLabel( pszLabel );
|
|
strLabel.ReleaseBuffer();
|
|
|
|
//
|
|
// Display an error message.
|
|
//
|
|
strMsg.FormatMessage( ADMC_IDS_EMPTY_RIGHT_LIST, pszLabel );
|
|
AppMessageBox( strMsg, MB_OK | MB_ICONWARNING );
|
|
|
|
bSuccess = FALSE;
|
|
} // if: list is empty and isn't allowed to be
|
|
} // if: saving data from the dialog
|
|
else
|
|
{
|
|
} // else: setting data to the dialog
|
|
|
|
return bSuccess;
|
|
|
|
} //*** UpdateData()
|
|
|
|
// Apply changes made on this dialog
|
|
BOOL BApplyChanges( void )
|
|
{
|
|
ATLASSERT( ! BIsStyleSet( LCPS_DONT_OUTPUT_RIGHT_LIST ) );
|
|
ATLASSERT( ! BReadOnly() );
|
|
|
|
T * pT = static_cast< T * >( this );
|
|
|
|
//
|
|
// Copy the Nodes list.
|
|
//
|
|
*pT->PlpobjRight() = LpobjRight();
|
|
|
|
//
|
|
// Call the base class method.
|
|
//
|
|
return BaseT::BApplyChanges();
|
|
|
|
} //*** BApplyChanges()
|
|
|
|
// Implementation
|
|
protected:
|
|
_objptrlist m_lpobjRight;
|
|
_objptrlist m_lpobjLeft;
|
|
CListViewCtrl m_lvcRight;
|
|
CListViewCtrl m_lvcLeft;
|
|
CListViewCtrl * m_plvcFocusList;
|
|
CButton m_pbAdd;
|
|
CButton m_pbRemove;
|
|
CButton m_pbMoveUp;
|
|
CButton m_pbMoveDown;
|
|
CButton m_pbProperties;
|
|
|
|
public:
|
|
//
|
|
// Message map.
|
|
//
|
|
BEGIN_MSG_MAP( thisClass )
|
|
MESSAGE_HANDLER( WM_CONTEXTMENU, OnContextMenu )
|
|
COMMAND_HANDLER( ADMC_IDC_LCP_ADD, BN_CLICKED, OnAdd )
|
|
COMMAND_HANDLER( ADMC_IDC_LCP_REMOVE, BN_CLICKED, OnRemove )
|
|
COMMAND_HANDLER( ADMC_IDC_LCP_MOVE_UP, BN_CLICKED, OnMoveUp )
|
|
COMMAND_HANDLER( ADMC_IDC_LCP_MOVE_DOWN, BN_CLICKED, OnMoveDown )
|
|
COMMAND_HANDLER( ADMC_IDC_LCP_PROPERTIES, BN_CLICKED, OnProperties )
|
|
COMMAND_HANDLER( IDOK, BN_CLICKED, OnOK )
|
|
COMMAND_HANDLER( IDCANCEL, BN_CLICKED, OnCancel )
|
|
COMMAND_HANDLER( ADMC_ID_MENU_PROPERTIES, 0, OnProperties )
|
|
NOTIFY_HANDLER( ADMC_IDC_LCP_LEFT_LIST, NM_DBLCLK, OnDblClkList )
|
|
NOTIFY_HANDLER( ADMC_IDC_LCP_RIGHT_LIST, NM_DBLCLK, OnDblClkList )
|
|
NOTIFY_HANDLER( ADMC_IDC_LCP_LEFT_LIST, LVN_ITEMCHANGED, OnItemChangedList )
|
|
NOTIFY_HANDLER( ADMC_IDC_LCP_RIGHT_LIST, LVN_ITEMCHANGED, OnItemChangedList )
|
|
NOTIFY_HANDLER( ADMC_IDC_LCP_LEFT_LIST, LVN_COLUMNCLICK, OnColumnClickList )
|
|
NOTIFY_HANDLER( ADMC_IDC_LCP_RIGHT_LIST, LVN_COLUMNCLICK, OnColumnClickList )
|
|
CHAIN_MSG_MAP( BaseT )
|
|
END_MSG_MAP()
|
|
|
|
//
|
|
// Message handler functions.
|
|
//
|
|
|
|
// Handler for WM_CONTEXTMENU
|
|
LRESULT OnContextMenu(
|
|
IN UINT uMsg,
|
|
IN WPARAM wParam,
|
|
IN LPARAM lParam,
|
|
IN OUT BOOL & bHandled
|
|
)
|
|
{
|
|
BOOL bDisplayed = FALSE;
|
|
CMenu * pmenu = NULL;
|
|
HWND hWnd = (HWND) wParam;
|
|
WORD xPos = LOWORD( lParam );
|
|
WORD yPos = HIWORD( lParam );
|
|
CListViewCtrl * plvc;
|
|
CString strMenuName;
|
|
CWaitCursor wc;
|
|
|
|
//
|
|
// If focus is not in a list control, don't handle the message.
|
|
//
|
|
if ( hWnd == m_lvcLeft.m_hWnd )
|
|
{
|
|
plvc = &m_lvcLeft;
|
|
} // if: context menu on left list
|
|
else if ( hWnd == m_lvcRight.m_hWnd )
|
|
{
|
|
plvc = &m_lvcRight;
|
|
} // else if: context menu on right list
|
|
else
|
|
{
|
|
bHandled = FALSE;
|
|
return 0;
|
|
} // else: focus not in a list control
|
|
ATLASSERT( plvc != NULL );
|
|
|
|
//
|
|
// If the properties button is not enabled, don't display a menu.
|
|
//
|
|
if ( ! BPropertiesButton() )
|
|
{
|
|
bHandled = FALSE;
|
|
return 0;
|
|
} // if: no properties button
|
|
|
|
//
|
|
// Create the menu to display.
|
|
//
|
|
pmenu = new CMenu;
|
|
ATLASSERT( pmenu != NULL );
|
|
if ( pmenu == NULL )
|
|
{
|
|
bHandled = FALSE;
|
|
return 0;
|
|
} // if: error allocating memory for the new menu
|
|
|
|
if ( pmenu->CreatePopupMenu() )
|
|
{
|
|
UINT nFlags = MF_STRING;
|
|
|
|
//
|
|
// If there are no items in the list, disable the menu item.
|
|
//
|
|
if ( plvc->GetItemCount() == 0 )
|
|
{
|
|
nFlags |= MF_GRAYED;
|
|
} // if: no items in the list
|
|
|
|
//
|
|
// Add the Properties item to the menu.
|
|
//
|
|
strMenuName.LoadString( ADMC_ID_MENU_PROPERTIES );
|
|
if ( pmenu->AppendMenu( nFlags, ADMC_ID_MENU_PROPERTIES, strMenuName ) )
|
|
{
|
|
m_plvcFocusList = plvc;
|
|
bDisplayed = TRUE;
|
|
} // if: successfully added menu item
|
|
} // if: menu created successfully
|
|
|
|
if ( bDisplayed )
|
|
{
|
|
//
|
|
// Display the menu.
|
|
//
|
|
if ( ! pmenu->TrackPopupMenu(
|
|
TPM_LEFTALIGN | TPM_RIGHTBUTTON,
|
|
xPos,
|
|
yPos,
|
|
m_hWnd
|
|
) )
|
|
{
|
|
} // if: unsuccessfully displayed the menu
|
|
} // if: there is a menu to display
|
|
|
|
delete pmenu;
|
|
return 0;
|
|
|
|
} //*** OnContextMenu()
|
|
|
|
// Handler for BN_CLICKED on ADMC_IDC_LCP_ADD
|
|
LRESULT OnAdd(
|
|
IN WORD wNotifyCode,
|
|
IN WORD idCtrl,
|
|
IN HWND hwndCtrl,
|
|
IN OUT BOOL & bHandled
|
|
)
|
|
{
|
|
ATLASSERT( ! BReadOnly() );
|
|
|
|
//
|
|
// Move selected items from the left list to the right list.
|
|
//
|
|
MoveItems( m_lvcRight, LpobjRight(), m_lvcLeft, LpobjLeft() );
|
|
|
|
return 0;
|
|
|
|
} //*** OnAdd()
|
|
|
|
// Handler for BN_CLICKED on ADMC_IDC_LCP_REMOVE
|
|
LRESULT OnRemove(
|
|
IN WORD wNotifyCode,
|
|
IN WORD idCtrl,
|
|
IN HWND hwndCtrl,
|
|
IN OUT BOOL & bHandled
|
|
)
|
|
{
|
|
ATLASSERT( ! BReadOnly() );
|
|
|
|
//
|
|
// Move selected items from the right list to the left list.
|
|
//
|
|
MoveItems( m_lvcLeft, LpobjLeft(), m_lvcRight, LpobjRight() );
|
|
|
|
return 0;
|
|
|
|
} //*** OnRemove()
|
|
|
|
// Handler for BN_CLICKED on ADMC_IDC_LCP_MOVE_UP
|
|
LRESULT OnMoveUp(
|
|
IN WORD wNotifyCode,
|
|
IN WORD idCtrl,
|
|
IN HWND hwndCtrl,
|
|
IN OUT BOOL & bHandled
|
|
)
|
|
{
|
|
int nItem;
|
|
ObjT * pobj;
|
|
|
|
//
|
|
// Find the index of the selected item.
|
|
//
|
|
nItem = m_lvcRight.GetNextItem( -1, LVNI_SELECTED );
|
|
ATLASSERT( nItem != -1 );
|
|
|
|
//
|
|
// Get the item pointer.
|
|
//
|
|
pobj = (ObjT *) m_lvcRight.GetItemData( nItem );
|
|
ATLASSERT( pobj != NULL );
|
|
|
|
// Remove the selected item from the list and add it back in.
|
|
{
|
|
_objptrlistit itRemove;
|
|
_objptrlistit itAdd;
|
|
|
|
// Find the position of the item to be removed and the item before
|
|
// which the item is to be inserted.
|
|
itRemove = std::find( LpobjRight().begin(), LpobjRight().end(), pobj );
|
|
ATLASSERT( itRemove != LpobjRight().end() );
|
|
itAdd = itRemove--;
|
|
LpobjRight().insert( itAdd, pobj );
|
|
LpobjRight().erase( itRemove );
|
|
} // Remove the selected item from the list and add it back in
|
|
|
|
// Remove the selected item from the list control and add it back in.
|
|
m_lvcRight.DeleteItem( nItem );
|
|
NInsertItemInListCtrl( nItem - 1, pobj, m_lvcRight );
|
|
m_lvcRight.SetItemState(
|
|
nItem - 1,
|
|
LVIS_SELECTED | LVIS_FOCUSED,
|
|
LVIS_SELECTED | LVIS_FOCUSED
|
|
);
|
|
m_lvcRight.EnsureVisible( nItem - 1, FALSE /*bPartialOK*/ );
|
|
m_lvcRight.SetFocus();
|
|
|
|
return 0;
|
|
|
|
} //*** OnMoveUp()
|
|
|
|
// Handler for BN_CLICKED on ADMC_IDC_LCP_MOVE_DOWN
|
|
LRESULT OnMoveDown(
|
|
IN WORD wNotifyCode,
|
|
IN WORD idCtrl,
|
|
IN HWND hwndCtrl,
|
|
IN OUT BOOL & bHandled
|
|
)
|
|
{
|
|
int nItem;
|
|
ObjT * pobj;
|
|
|
|
//
|
|
// Find the index of the selected item.
|
|
//
|
|
nItem = m_lvcRight.GetNextItem( -1, LVNI_SELECTED );
|
|
ATLASSERT( nItem != -1 );
|
|
|
|
//
|
|
// Get the item pointer.
|
|
//
|
|
pobj = (ObjT *) m_lvcRight.GetItemData( nItem );
|
|
ATLASSERT( pobj != NULL );
|
|
|
|
// Remove the selected item from the list and add it back in.
|
|
{
|
|
_objptrlistit itRemove;
|
|
_objptrlistit itAdd;
|
|
|
|
// Find the position of the item to be removed and the item after
|
|
// which the item is to be inserted.
|
|
itRemove = std::find( LpobjRight().begin(), LpobjRight().end(), pobj );
|
|
ATLASSERT( itRemove != LpobjRight().end() );
|
|
itAdd = itRemove++;
|
|
LpobjRight().insert( itAdd, pobj );
|
|
LpobjRight().erase( itRemove );
|
|
} // Remove the selected item from the list and add it back in
|
|
|
|
// Remove the selected item from the list control and add it back in.
|
|
m_lvcRight.DeleteItem( nItem );
|
|
NInsertItemInListCtrl( nItem + 1, pobj, m_lvcRight );
|
|
m_lvcRight.SetItemState(
|
|
nItem + 1,
|
|
LVIS_SELECTED | LVIS_FOCUSED,
|
|
LVIS_SELECTED | LVIS_FOCUSED
|
|
);
|
|
m_lvcRight.EnsureVisible( nItem + 1, FALSE /*bPartialOK*/ );
|
|
m_lvcRight.SetFocus();
|
|
|
|
return 0;
|
|
|
|
} //*** OnMoveDown()
|
|
|
|
// Handler for BN_CLICKED on ADMC_IDC_LCP_PROPERTIES
|
|
LRESULT OnProperties(
|
|
IN WORD wNotifyCode,
|
|
IN WORD idCtrl,
|
|
IN HWND hwndCtrl,
|
|
IN OUT BOOL & bHandled
|
|
)
|
|
{
|
|
int iitem;
|
|
ObjT * pobj;
|
|
|
|
ATLASSERT( m_plvcFocusList != NULL );
|
|
|
|
// Get the index of the item with the focus.
|
|
iitem = m_plvcFocusList->GetNextItem( -1, LVNI_FOCUSED );
|
|
ATLASSERT( iitem != -1 );
|
|
|
|
// Get a pointer to the selected item.
|
|
pobj = (ObjT *) m_plvcFocusList->GetItemData( iitem );
|
|
ATLASSERT( pobj != NULL );
|
|
|
|
T * pT = static_cast< T * >( this );
|
|
|
|
if ( pT->BDisplayProperties( pobj ) )
|
|
{
|
|
// Update this item.
|
|
{
|
|
CString strText;
|
|
int iimg = 0;
|
|
int icol;
|
|
|
|
pT->GetColumnInfo( pobj, iitem, 0, strText, &iimg );
|
|
m_plvcFocusList->SetItem( iitem, 0, LVIF_TEXT /*| LVIF_IMAGE*/, strText, iimg, 0, 0, 0 );
|
|
|
|
for ( icol = 1 ; icol < m_aColumns.size() ; icol++ )
|
|
{
|
|
pT->GetColumnInfo( pobj, iitem, icol, strText, NULL );
|
|
m_plvcFocusList->SetItemText( iitem, icol, strText );
|
|
} // for: each column
|
|
} // Update this item
|
|
} // if: properties changed
|
|
|
|
return 0;
|
|
|
|
} //*** OnProperties()
|
|
|
|
// Handler for BN_CLICKED on IDOK
|
|
LRESULT OnOK(
|
|
IN WORD wNotifyCode,
|
|
IN WORD idCtrl,
|
|
IN HWND hwndCtrl,
|
|
IN OUT BOOL & bHandled
|
|
)
|
|
{
|
|
//
|
|
// Save dialog data and exit the dialog.
|
|
//
|
|
if ( BSaveChanges() )
|
|
{
|
|
EndDialog( IDOK );
|
|
} // if: dialgo data saved
|
|
|
|
return 0;
|
|
|
|
} //*** OnOK()
|
|
|
|
// Handler for BN_CLICKED on IDCANCEL
|
|
LRESULT OnCancel(
|
|
IN WORD wNotifyCode,
|
|
IN WORD idCtrl,
|
|
IN HWND hwndCtrl,
|
|
IN OUT BOOL & bHandled
|
|
)
|
|
{
|
|
//
|
|
// Exit the dialog.
|
|
//
|
|
EndDialog( IDCANCEL );
|
|
return 0;
|
|
|
|
} //*** OnCancel()
|
|
|
|
// Handler for NM_DBLCLK on ADMC_IDC_LCP_LEFT_LIST & ADMC_IDC_LCP_RIGHT_LIST
|
|
LRESULT OnDblClkList(
|
|
IN WORD idCtrl,
|
|
IN LPNMHDR pnmh,
|
|
IN OUT BOOL & bHandled
|
|
)
|
|
{
|
|
ATLASSERT( ! BReadOnly() );
|
|
|
|
LRESULT lResult;
|
|
|
|
if ( idCtrl == ADMC_IDC_LCP_LEFT_LIST )
|
|
{
|
|
m_plvcFocusList = &m_lvcLeft;
|
|
lResult = OnAdd( BN_CLICKED, idCtrl, pnmh->hwndFrom, bHandled );
|
|
} // if: double-clicked in left list
|
|
else if ( idCtrl == ADMC_IDC_LCP_RIGHT_LIST )
|
|
{
|
|
m_plvcFocusList = &m_lvcRight;
|
|
lResult = OnRemove( BN_CLICKED, idCtrl, pnmh->hwndFrom, bHandled );
|
|
} // else if: double-clicked in right list
|
|
else
|
|
{
|
|
ATLASSERT( 0 );
|
|
lResult = 0;
|
|
} // else: double-clicked in an unknown location
|
|
|
|
return lResult;
|
|
|
|
} //*** OnDblClkList()
|
|
|
|
// Handler for LVN_ITEMCHANGED on ADMC_IDC_LCP_LEFT_LIST & ADMC_IDC_LCP_RIGHT_LIST
|
|
LRESULT OnItemChangedList(
|
|
IN int idCtrl,
|
|
IN LPNMHDR pnmh,
|
|
IN OUT BOOL & bHandled
|
|
)
|
|
{
|
|
NM_LISTVIEW * pNMListView = (NM_LISTVIEW *) pnmh;
|
|
BOOL bEnable;
|
|
CButton * ppb;
|
|
|
|
if ( idCtrl == ADMC_IDC_LCP_LEFT_LIST )
|
|
{
|
|
m_plvcFocusList = &m_lvcLeft;
|
|
ppb = &m_pbAdd;
|
|
} // if: item changed in left list
|
|
else if ( idCtrl == ADMC_IDC_LCP_RIGHT_LIST )
|
|
{
|
|
m_plvcFocusList = &m_lvcRight;
|
|
ppb = &m_pbRemove;
|
|
} // else if: item changed in right list
|
|
else
|
|
{
|
|
ATLASSERT( 0 );
|
|
bHandled = FALSE;
|
|
return 0;
|
|
} // else: unknown list
|
|
ATLASSERT( ppb != NULL );
|
|
|
|
// If the selection changed, enable/disable the Add button.
|
|
if ( (pNMListView->uChanged & LVIF_STATE)
|
|
&& ( (pNMListView->uOldState & LVIS_SELECTED)
|
|
|| (pNMListView->uNewState & LVIS_SELECTED) )
|
|
&& ! BReadOnly() )
|
|
{
|
|
UINT cSelected = m_plvcFocusList->GetSelectedCount();
|
|
|
|
//
|
|
// If there is a selection, enable the Add or Remove button.
|
|
// Otherwise disable it.
|
|
//
|
|
bEnable = (cSelected != 0);
|
|
ppb->EnableWindow( bEnable );
|
|
if ( BPropertiesButton() )
|
|
{
|
|
m_pbProperties.EnableWindow( (cSelected == 1) ? TRUE : FALSE );
|
|
} // if: dialog has Properties button
|
|
|
|
//
|
|
// If the right list is ordered, setup the state of the Up/Down buttons.
|
|
//
|
|
if ( BOrdered() )
|
|
{
|
|
SetUpDownState();
|
|
} // if: right list is ordered
|
|
} // if: selection changed
|
|
|
|
return 0;
|
|
|
|
} //*** OnItemChangedList()
|
|
|
|
// Handler for LVN_COLUMNCLICK on ADMC_IDC_LCP_LEFT_LIST & ADMC_IDC_LCP_RIGHT_LIST
|
|
LRESULT OnColumnClickList(
|
|
IN int idCtrl,
|
|
IN LPNMHDR pnmh,
|
|
IN OUT BOOL & bHandled
|
|
)
|
|
{
|
|
NM_LISTVIEW * pNMListView = (NM_LISTVIEW *) pnmh;
|
|
|
|
if ( idCtrl == ADMC_IDC_LCP_LEFT_LIST )
|
|
{
|
|
m_plvcFocusList = &m_lvcLeft;
|
|
m_psiCur = &SiLeft();
|
|
} // if: column clicked in left list
|
|
else if ( idCtrl == ADMC_IDC_LCP_RIGHT_LIST )
|
|
{
|
|
m_plvcFocusList = &m_lvcRight;
|
|
m_psiCur = &SiRight();
|
|
} // else if: column clicked in right list
|
|
else
|
|
{
|
|
ATLASSERT( 0 );
|
|
bHandled = FALSE;
|
|
return 0;
|
|
} // else: column clicked in unknown list
|
|
|
|
// Save the current sort column and direction.
|
|
if ( pNMListView->iSubItem == m_psiCur->m_nColumn )
|
|
{
|
|
m_psiCur->m_nDirection ^= -1;
|
|
} // if: sorting same column again
|
|
else
|
|
{
|
|
m_psiCur->m_nColumn = pNMListView->iSubItem;
|
|
m_psiCur->m_nDirection = 0;
|
|
} // else: different column
|
|
|
|
// Sort the list.
|
|
if ( ! m_plvcFocusList->SortItems( CompareItems, (LPARAM) this ) )
|
|
{
|
|
ATLASSERT( 0 );
|
|
} // if: error sorting items
|
|
|
|
return 0;
|
|
|
|
} //*** OnColumnClickList
|
|
|
|
//
|
|
// Message handler overrides.
|
|
//
|
|
|
|
// Handler for the WM_INITDIALOG message
|
|
BOOL OnInitDialog( void )
|
|
{
|
|
#if DBG
|
|
T * pT = static_cast< T * >( this );
|
|
ATLASSERT( pT->PlpobjRight() != NULL );
|
|
ATLASSERT( pT->PlpobjLeft() != NULL );
|
|
#endif // DBG
|
|
|
|
//
|
|
// Attach the controls to control member variables.
|
|
//
|
|
AttachControl( m_lvcRight, ADMC_IDC_LCP_RIGHT_LIST );
|
|
AttachControl( m_lvcLeft, ADMC_IDC_LCP_LEFT_LIST );
|
|
AttachControl( m_pbAdd, ADMC_IDC_LCP_ADD );
|
|
AttachControl( m_pbRemove, ADMC_IDC_LCP_REMOVE );
|
|
if ( BPropertiesButton() )
|
|
{
|
|
AttachControl( m_pbProperties, ADMC_IDC_LCP_PROPERTIES );
|
|
} // if: dialog has Properties button
|
|
if ( BCanBeOrdered() )
|
|
{
|
|
AttachControl( m_pbMoveUp, ADMC_IDC_LCP_MOVE_UP );
|
|
AttachControl( m_pbMoveDown, ADMC_IDC_LCP_MOVE_DOWN );
|
|
} // if: left list can be ordered
|
|
|
|
// if ( BShowImages() )
|
|
// {
|
|
// CClusterAdminApp * papp = GetClusterAdminApp();
|
|
//
|
|
// m_lvcLeft.SetImageList( papp->PilSmallImages(), LVSIL_SMALL );
|
|
// m_lvcRight.SetImageList( papp->PilSmallImages(), LVSIL_SMALL );
|
|
// } // if: showing images
|
|
|
|
//
|
|
// Disable buttons by default.
|
|
//
|
|
m_pbAdd.EnableWindow( FALSE );
|
|
m_pbRemove.EnableWindow( FALSE );
|
|
if ( BPropertiesButton() )
|
|
{
|
|
m_pbProperties.EnableWindow( FALSE );
|
|
} // if: dialog has Properties button
|
|
|
|
//
|
|
// Set the right list to sort if not ordered. Set both to show selection always.
|
|
//
|
|
if ( BOrdered() )
|
|
{
|
|
m_lvcRight.ModifyStyle( 0, LVS_SHOWSELALWAYS, 0 );
|
|
} // if: right list is ordered
|
|
else
|
|
{
|
|
m_lvcRight.ModifyStyle( 0, LVS_SHOWSELALWAYS | LVS_SORTASCENDING, 0 );
|
|
} // else: right list is not ordered
|
|
m_lvcLeft.ModifyStyle( 0, LVS_SHOWSELALWAYS, 0 );
|
|
|
|
|
|
//
|
|
// If this is an ordered list, show the Move buttons.
|
|
// Otherwise, hide them.
|
|
//
|
|
if ( BCanBeOrdered() )
|
|
{
|
|
SetUpDownState();
|
|
} // if: list can be ordered
|
|
|
|
//
|
|
// Change left list view control extended styles.
|
|
//
|
|
m_lvcLeft.SetExtendedListViewStyle(
|
|
LVS_EX_FULLROWSELECT | LVS_EX_HEADERDRAGDROP,
|
|
LVS_EX_FULLROWSELECT | LVS_EX_HEADERDRAGDROP
|
|
);
|
|
|
|
//
|
|
// Change right list view control extended styles.
|
|
//
|
|
m_lvcRight.SetExtendedListViewStyle(
|
|
LVS_EX_FULLROWSELECT | LVS_EX_HEADERDRAGDROP,
|
|
LVS_EX_FULLROWSELECT | LVS_EX_HEADERDRAGDROP
|
|
);
|
|
|
|
// Duplicate lists.
|
|
DuplicateLists();
|
|
|
|
//
|
|
// Insert all the columns.
|
|
//
|
|
{
|
|
int icol;
|
|
int ncol;
|
|
int nUpperBound = m_aColumns.size();
|
|
CString strColText;
|
|
|
|
ATLASSERT( nUpperBound > 0 );
|
|
|
|
for ( icol = 0 ; icol < nUpperBound ; icol++ )
|
|
{
|
|
strColText.LoadString( m_aColumns[icol].m_idsText );
|
|
ncol = m_lvcLeft.InsertColumn( icol, strColText, LVCFMT_LEFT, m_aColumns[icol].m_nWidth, 0 );
|
|
ATLASSERT( ncol == icol );
|
|
ncol = m_lvcRight.InsertColumn( icol, strColText, LVCFMT_LEFT, m_aColumns[icol].m_nWidth, 0 );
|
|
ATLASSERT( ncol == icol );
|
|
} // for: each column
|
|
} // Insert all the columns
|
|
|
|
//
|
|
// Fill the list controls.
|
|
//
|
|
FillList( m_lvcRight, LpobjRight() );
|
|
FillList( m_lvcLeft, LpobjLeft() );
|
|
|
|
//
|
|
// If read-only, set all controls to be either disabled or read-only.
|
|
//
|
|
if ( BReadOnly() )
|
|
{
|
|
m_lvcRight.EnableWindow( FALSE );
|
|
m_lvcLeft.EnableWindow( FALSE );
|
|
} // if: sheet is read-only
|
|
|
|
//
|
|
// Call the base class method.
|
|
//
|
|
return BaseT::OnInitDialog();
|
|
|
|
} //*** OnInitDialog()
|
|
|
|
// Handler for PSN_SETACTIVE
|
|
BOOL OnSetActive( void )
|
|
{
|
|
UINT nSelCount;
|
|
|
|
// Set the focus to the left list.
|
|
m_lvcLeft.SetFocus();
|
|
m_plvcFocusList = &m_lvcLeft;
|
|
|
|
// Enable/disable the Properties button.
|
|
nSelCount = m_lvcLeft.GetSelectedCount();
|
|
if ( BPropertiesButton() )
|
|
{
|
|
m_pbProperties.EnableWindow( nSelCount == 1 );
|
|
} // if: dialog has Properties button
|
|
|
|
// Enable or disable the other buttons.
|
|
if ( ! BReadOnly() )
|
|
{
|
|
m_pbAdd.EnableWindow( nSelCount > 0 );
|
|
nSelCount = m_lvcRight.GetSelectedCount();
|
|
m_pbRemove.EnableWindow( nSelCount > 0 );
|
|
SetUpDownState();
|
|
} // if: not read-only page
|
|
|
|
return TRUE;
|
|
|
|
} //*** OnSetActive()
|
|
|
|
public:
|
|
_objptrlist & LpobjRight( void ) { return m_lpobjRight; }
|
|
_objptrlist & LpobjLeft( void ) { return m_lpobjLeft; }
|
|
|
|
protected:
|
|
void DuplicateLists( void )
|
|
{
|
|
LpobjRight().erase( LpobjRight().begin(), LpobjRight().end() );
|
|
LpobjLeft().erase( LpobjLeft().begin(), LpobjLeft().end() );
|
|
|
|
T * pT = static_cast< T * >( this );
|
|
|
|
if ( (pT->PlpobjRight() == NULL) || (pT->PlpobjLeft() == NULL) )
|
|
{
|
|
return;
|
|
} // if: either list is empty
|
|
|
|
//
|
|
// Duplicate the lists.
|
|
//
|
|
LpobjRight() = *pT->PlpobjRight();
|
|
LpobjLeft() = *pT->PlpobjLeft();
|
|
|
|
//
|
|
// Remove all the items that are in the right list from
|
|
// the left list.
|
|
//
|
|
_objptrlistit itRight;
|
|
_objptrlistit itLeft;
|
|
for ( itRight = LpobjRight().begin()
|
|
; itRight != LpobjRight().end()
|
|
; itRight++ )
|
|
{
|
|
//
|
|
// Find the item in the left list.
|
|
//
|
|
itLeft = std::find( LpobjLeft().begin(), LpobjLeft().end(), *itRight );
|
|
if ( itLeft != LpobjLeft().end() )
|
|
{
|
|
LpobjLeft().erase( itLeft );
|
|
} // if: object found in left list
|
|
} // for: each item in the right list
|
|
|
|
} //*** DuplicateLists()
|
|
|
|
// Fill a list control
|
|
void FillList( IN OUT CListViewCtrl & rlvc, IN const _objptrlist & rlpobj )
|
|
{
|
|
_objptrlistit itpobj;
|
|
ObjT * pobj;
|
|
int iItem;
|
|
|
|
// Initialize the control.
|
|
if ( ! rlvc.DeleteAllItems() )
|
|
{
|
|
ATLASSERT( 0 );
|
|
} // if: error deleting all items
|
|
|
|
rlvc.SetItemCount( rlpobj.size() );
|
|
|
|
// Add the items to the list.
|
|
itpobj = rlpobj.begin();
|
|
for ( iItem = 0 ; itpobj != rlpobj.end() ; iItem++, itpobj++ )
|
|
{
|
|
pobj = *itpobj;
|
|
NInsertItemInListCtrl( iItem, pobj, rlvc );
|
|
} // for: each string in the list
|
|
|
|
// If there are any items, set the focus on the first one.
|
|
if ( rlvc.GetItemCount() != 0)
|
|
{
|
|
rlvc.SetItemState( 0, LVIS_FOCUSED, LVIS_FOCUSED );
|
|
} // if: items were added to the list
|
|
|
|
} //*** FillList()
|
|
|
|
// Move items from one list to another
|
|
void MoveItems(
|
|
IN OUT CListViewCtrl & rlvcDst,
|
|
IN OUT _objptrlist & rlpobjDst,
|
|
IN OUT CListViewCtrl & rlvcSrc,
|
|
IN OUT _objptrlist & rlpobjSrc
|
|
)
|
|
{
|
|
int iSrcItem;
|
|
int iDstItem;
|
|
int nItem = -1;
|
|
ObjT * pobj;
|
|
_objptrlistit itpobj;
|
|
|
|
ATLASSERT( ! BReadOnly() );
|
|
|
|
iDstItem = rlvcDst.GetItemCount();
|
|
while ( (iSrcItem = rlvcSrc.GetNextItem( -1, LVNI_SELECTED )) != -1 )
|
|
{
|
|
// Get the item pointer.
|
|
pobj = (ObjT *) rlvcSrc.GetItemData( iSrcItem );
|
|
ATLASSERT( pobj );
|
|
|
|
// Remove the item from the source list.
|
|
itpobj = std::find( rlpobjSrc.begin(), rlpobjSrc.end(), pobj );
|
|
ATLASSERT( itpobj != rlpobjSrc.end() );
|
|
rlpobjSrc.remove( *itpobj );
|
|
|
|
// Add the item to the destination list.
|
|
rlpobjDst.insert( rlpobjDst.end(), pobj );
|
|
|
|
// Remove the item from the source list control and
|
|
// add it to the destination list control.
|
|
if ( ! rlvcSrc.DeleteItem( iSrcItem ) )
|
|
{
|
|
ATLASSERT( 0 );
|
|
} // if: error deleting the item
|
|
nItem = NInsertItemInListCtrl( iDstItem++, pobj, rlvcDst );
|
|
rlvcDst.SetItemState(
|
|
nItem,
|
|
LVIS_SELECTED | LVIS_FOCUSED,
|
|
LVIS_SELECTED | LVIS_FOCUSED
|
|
);
|
|
} // while: more items
|
|
|
|
ATLASSERT( nItem != -1 );
|
|
|
|
rlvcDst.EnsureVisible( nItem, FALSE /*bPartialOK*/ );
|
|
rlvcDst.SetFocus();
|
|
|
|
// Indicate that the data has changed.
|
|
::SendMessage( GetParent(), PSM_CHANGED, (WPARAM) m_hWnd, NULL );
|
|
|
|
} //*** MoveItems()
|
|
BOOL BSaveChanges( void )
|
|
{
|
|
ATLASSERT( ! BIsStyleSet( LCPS_DONT_OUTPUT_RIGHT_LIST ) );
|
|
ATLASSERT( ! BReadOnly() );
|
|
|
|
T * pT = static_cast< T * >( this );
|
|
|
|
//
|
|
// Update the data first.
|
|
//
|
|
if ( ! pT->UpdateData( TRUE /*bSaveAndValidate*/ ) )
|
|
{
|
|
return FALSE;
|
|
} // if: error updating data
|
|
|
|
//
|
|
// Copy the object list.
|
|
//
|
|
*pT->PlpobjRight() = LpobjRight();
|
|
|
|
return TRUE;
|
|
|
|
} //*** BSaveChanges()
|
|
|
|
// Set the state of the Up/Down buttons based on the selection.
|
|
void SetUpDownState( void )
|
|
{
|
|
BOOL bEnableUp;
|
|
BOOL bEnableDown;
|
|
|
|
if ( BOrdered()
|
|
&& ! BReadOnly()
|
|
&& (m_lvcRight.GetSelectedCount() == 1) )
|
|
{
|
|
int nItem;
|
|
|
|
bEnableUp = TRUE;
|
|
bEnableDown = TRUE;
|
|
|
|
//
|
|
// Find the index of the selected item.
|
|
//
|
|
nItem = m_lvcRight.GetNextItem( -1, LVNI_SELECTED );
|
|
ATLASSERT( nItem != -1 );
|
|
|
|
//
|
|
// If the first item is selected, can't move up.
|
|
//
|
|
if ( nItem == 0 )
|
|
{
|
|
bEnableUp = FALSE;
|
|
} // if: first item is selected
|
|
|
|
//
|
|
// If the last item is selected, can't move down.
|
|
//
|
|
if ( nItem == m_lvcRight.GetItemCount() - 1 )
|
|
{
|
|
bEnableDown = FALSE;
|
|
} // if: last item is selected
|
|
} // if: only one item selected
|
|
else
|
|
{
|
|
bEnableUp = FALSE;
|
|
bEnableDown = FALSE;
|
|
} // else: zero or more than one item selected
|
|
|
|
m_pbMoveUp.EnableWindow( bEnableUp );
|
|
m_pbMoveDown.EnableWindow( bEnableDown );
|
|
|
|
} //*** SetUpDownState()
|
|
|
|
static int CALLBACK CompareItems( LPARAM lparam1, LPARAM lparam2, LPARAM lparamSort )
|
|
{
|
|
ObjT * pobj1 = reinterpret_cast< ObjT * >( lparam1 );
|
|
ObjT * pobj2 = reinterpret_cast< ObjT * >( lparam2 );
|
|
T * plcp = reinterpret_cast< T * >( lparamSort );
|
|
SortInfo * psiCur = plcp->PsiCur();
|
|
int icol = psiCur->m_nColumn;
|
|
int nResult;
|
|
CString str1;
|
|
CString str2;
|
|
|
|
ATLASSERT( pobj1 != NULL );
|
|
ATLASSERT( pobj2 != NULL );
|
|
ATLASSERT( plcp != NULL );
|
|
ATLASSERT( psiCur->m_nColumn >= 0 );
|
|
ATLASSERT( icol >= 0 );
|
|
|
|
plcp->GetColumnInfo( pobj1, 0, icol, str1, NULL );
|
|
plcp->GetColumnInfo( pobj2, 0, icol, str2, NULL );
|
|
|
|
nResult = str1.Compare( str2 );
|
|
|
|
// Return the result based on the direction we are sorting.
|
|
if ( psiCur->m_nDirection != 0 )
|
|
{
|
|
nResult = -nResult;
|
|
} // if: sorting in reverse direction
|
|
|
|
return nResult;
|
|
|
|
} //*** CompareItems()
|
|
|
|
SortInfo m_siLeft;
|
|
SortInfo m_siRight;
|
|
SortInfo * m_psiCur;
|
|
|
|
SortInfo & SiLeft( void ) { return m_siLeft; }
|
|
SortInfo & SiRight( void ) { return m_siRight; }
|
|
|
|
public:
|
|
SortInfo * PsiCur( void ) const { return m_psiCur; }
|
|
|
|
}; //*** class CListCtrlPair
|
|
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
|
|
#endif // __ATLLCPAIR_H_
|