// MapWPge.cpp : implementation file // #include "stdafx.h" #include #include "certmap.h" extern "C" { #include #include } #include "Iismap.hxx" #include "Iiscmr.hxx" #include "brwsdlg.h" #include "ListRow.h" #include "ChkLstCt.h" #include "MapWPge.h" #include "Ed11Maps.h" #include "EdWldRul.h" #include #include "wrapmb.h" #include "WWzOne.h" #include "WWzTwo.h" #include "WWzThree.h" #include #ifdef _DEBUG #define new DEBUG_NEW #undef THIS_FILE static char THIS_FILE[] = __FILE__; #endif #define COL_NUM_ENABLED 0 #define COL_NUM_DESCRIPTION 1 #define COL_NUM_NTACCOUNT 2 // // valid only when accessing IIS5.1 or IIS5 // #define MB_EXTEND_KEY "CertW" ///////////////////////////////////////////////////////////////////////////// // CMapWildcardsPge property page IMPLEMENT_DYNCREATE(CMapWildcardsPge, CPropertyPage) //--------------------------------------------------------------------------- CMapWildcardsPge::CMapWildcardsPge() : CPropertyPage(CMapWildcardsPge::IDD), m_fDirty(FALSE) { //{{AFX_DATA_INIT(CMapWildcardsPge) m_bool_enable = FALSE; //}}AFX_DATA_INIT m_fIsIIS6 = TRUE; } //--------------------------------------------------------------------------- CMapWildcardsPge::~CMapWildcardsPge() { } //--------------------------------------------------------------------------- void CMapWildcardsPge::DoDataExchange(CDataExchange* pDX) { CPropertyPage::DoDataExchange(pDX); //{{AFX_DATA_MAP(CMapWildcardsPge) DDX_Control(pDX, IDC_LIST, m_clistctrl_list); DDX_Control(pDX, IDC_MOVE_UP, m_cbutton_up); DDX_Control(pDX, IDC_MOVE_DOWN, m_cbutton_down); DDX_Control(pDX, IDC_ADD, m_cbutton_add); DDX_Control(pDX, IDC_DELETE, m_cbutton_delete); DDX_Control(pDX, IDC_EDIT, m_cbutton_editrule); DDX_Check(pDX, IDC_ENABLE, m_bool_enable); //}}AFX_DATA_MAP } //--------------------------------------------------------------------------- BEGIN_MESSAGE_MAP(CMapWildcardsPge, CPropertyPage) //{{AFX_MSG_MAP(CMapWildcardsPge) ON_BN_CLICKED(IDC_MOVE_DOWN, OnMoveDown) ON_BN_CLICKED(IDC_MOVE_UP, OnMoveUp) ON_BN_CLICKED(IDC_ADD, OnAdd) ON_BN_CLICKED(IDC_DELETE, OnDelete) ON_BN_CLICKED(IDC_EDIT, OnEdit) ON_NOTIFY(LVN_ITEMCHANGED, IDC_LIST, OnItemchangedList) ON_NOTIFY(NM_DBLCLK, IDC_LIST, OnDblclkList) ON_BN_CLICKED(IDC_ENABLE, OnEnable) //}}AFX_MSG_MAP ON_COMMAND(ID_HELP_FINDER, DoHelp) ON_COMMAND(ID_HELP, DoHelp) ON_COMMAND(ID_CONTEXT_HELP, DoHelp) ON_COMMAND(ID_DEFAULT_HELP, DoHelp) END_MESSAGE_MAP() //--------------------------------------------------------------------------- void CMapWildcardsPge::DoHelp() { WinHelpDebug(HIDD_CERTMAP_MAIN_ADVANCED); WinHelp( HIDD_CERTMAP_MAIN_ADVANCED ); } ///////////////////////////////////////////////////////////////////////////// // initialization routines //--------------------------------------------------------------------------- // FInitMapper is called by the routine instantiating this page. After the object // is first created is when it is called. It allows us to fail gracefully. BOOL CMapWildcardsPge::FInit(IMSAdminBase* pMB) { BOOL fAnswer = FALSE; PVOID pData = NULL; DWORD cbData = 0; BOOL f; m_pMB = pMB; // before messing with the metabase, prepare the strings we will need CString szObjectPath; // check if it's for older than iis6 version m_fIsIIS6 = TRUE; if (IsLegacyMetabase(pMB)){m_fIsIIS6 = FALSE;} if ( !m_fIsIIS6 ) { szObjectPath = m_szMBPath + SZ_NAMESPACE_EXTENTION + _T('/') + MB_EXTEND_KEY; } else { szObjectPath = m_szMBPath; } // prepare the metabase wrapper CWrapMetaBase mbWrap; f = mbWrap.FInit(m_pMB); if ( !f ) return FALSE; // attempt to open the object we want to store into f = mbWrap.Open( szObjectPath, METADATA_PERMISSION_READ ); // if that worked, load the data if ( f ) { // first, get the size of the data that we are looking for pData = mbWrap.GetData( _T(""), MD_SERIAL_CERTW, IIS_MD_UT_SERVER, BINARY_METADATA, &cbData ); // if we successfully got the data, unserialize it // WARNING: m_mapper.Unserialize changes the value of the pointer that is passed in. Pass // in a copy of the pointer PUCHAR pDataCopy = (PUCHAR)pData; if ( pData && (cbData > 0)) fAnswer = m_mapper.Unserialize( (PUCHAR*)&pDataCopy, &cbData ); // close the object f = mbWrap.Close(); // cleanup if ( pData ) mbWrap.FreeWrapData( pData ); } // return the answer return fAnswer; } //--------------------------------------------------------------------------- BOOL CMapWildcardsPge::OnInitDialog() { //call the parental oninitdialog BOOL f = CPropertyPage::OnInitDialog(); // if the initinalization (sp?) succeeded, init the list and other items if ( f ) { // init the contents of the list FInitRulesList(); // Fill the mapping list with the stored items FillRulesList(); // set the initial button states EnableDependantButtons(); } // set the initial state of the enable button // get the globals object CCertGlobalRuleInfo* pGlob = m_mapper.GetGlobalRulesInfo(); m_bool_enable = pGlob->GetRulesEnabled(); // set any changes in the info into place UpdateData(FALSE); // return the answer return f; } //--------------------------------------------------------------------------- BOOL CMapWildcardsPge::FInitRulesList() { CString sz; int i; // setup the friendly name column sz.Empty(); i = m_clistctrl_list.InsertColumn( COL_NUM_ENABLED, sz, LVCFMT_LEFT, 20 ); // setup the description column sz.LoadString( IDS_WILD_DESCRIPTION ); i = m_clistctrl_list.InsertColumn( COL_NUM_DESCRIPTION, sz, LVCFMT_LEFT, 238 ); // setup the account column sz.LoadString( IDS_WILD_ACCOUNT ); i = m_clistctrl_list.InsertColumn( COL_NUM_NTACCOUNT, sz, LVCFMT_LEFT, 220 ); return TRUE; } //--------------------------------------------------------------------------- // fill in the rules. Get the order for the rules from the globals object. That // way there is no need to sort them later BOOL CMapWildcardsPge::FillRulesList() { // get the globals object CCertGlobalRuleInfo* pGlob = m_mapper.GetGlobalRulesInfo(); // get the number of rules (actually its a number of rule order - but they are the same thing) DWORD cbRules = m_mapper.GetRuleCount(); // get the pointer to the order array DWORD* pOrder = pGlob->GetRuleOrderArray(); // for each item in the mapper object, add it to the list control for ( DWORD j = 0; j < cbRules; j++ ) { CCertMapRule* pRule; DWORD iRule = pOrder[j]; // get the mapping pRule = m_mapper.GetRule( iRule ); // if that worked, add it to the list if ( pRule ) { // add it to the list AddRuleToList( pRule, iRule, 0xffffffff ); } } // it worked - so ok. return TRUE; } //--------------------------------------------------------------------------- int CMapWildcardsPge::AddRuleToList( CCertMapRule* pRule, DWORD iRule, int iInsert ) { CString sz; int i; if ( !pRule ) return -1; // if the item to be inserted is to be the last, set it up if ( iInsert == 0xffffffff ) iInsert = m_clistctrl_list.GetItemCount(); // get the appropriate "enabled" string BOOL fEnabled = pRule->GetRuleEnabled(); if ( fEnabled ) sz.LoadString( IDS_ENABLED ); else sz.Empty(); // add the friendly name of the mapping // create the new entry in the list box. Do not sort on this entry - yet i = m_clistctrl_list.InsertItem( iInsert, sz ); // add the friendly name of the rule sz = pRule->GetRuleName(); // create the new entry in the list box. Do not sort on this entry - yet m_clistctrl_list.SetItemText( i, COL_NUM_DESCRIPTION, sz ); // add the account name of the mapping if ( pRule->GetRuleDenyAccess() ) sz.LoadString( IDS_DENYACCESS ); else sz = pRule->GetRuleAccount(); m_clistctrl_list.SetItemText( i, COL_NUM_NTACCOUNT, sz ); // attach the mapper index to the item in the list - it may have a different // list index after the list has been sorted. m_clistctrl_list.SetItemData( i, iRule ); // return whether or not the insertion succeeded return i; } //--------------------------------------------------------------------------- // Note: supposedly, the order of the items in the list and the odrder // of the items in the globals object should be the same void CMapWildcardsPge::UpdateRuleInDispList( DWORD iList, CCertMapRule* pRule ) { CString sz; // get the appropriate "enabled" string BOOL fEnabled = pRule->GetRuleEnabled(); if ( fEnabled ) sz.LoadString( IDS_ENABLED ); else sz.Empty(); // update the "Enabled" indicator m_clistctrl_list.SetItemText( iList, COL_NUM_ENABLED, sz ); // update the mapping name sz = pRule->GetRuleName(); m_clistctrl_list.SetItemText( iList, COL_NUM_DESCRIPTION, sz ); // update the account name if ( pRule->GetRuleDenyAccess() ) sz.LoadString( IDS_DENYACCESS ); else sz = pRule->GetRuleAccount(); m_clistctrl_list.SetItemText( iList, COL_NUM_NTACCOUNT, sz ); } //--------------------------------------------------------------------------- // editing a wildcard rule is rather complex, thus I am seperating that code // out into that for the dialog itself. All we do is pass in the rule pointer // and let it go at that. BOOL CMapWildcardsPge::EditOneRule( CCertMapRule* pRule, BOOL fAsWizard ) { // edit the item using a tabbed dialog / wizard CPropertySheet propSheet; CWildWizOne wwOne; CWildWizTwo wwTwo; CWildWizThree wwThree; // set the params wwOne.m_pMB = m_pMB; // fill in the data for the pages wwOne.m_pRule = pRule; wwOne.m_szMBPath = m_szMBPath; wwOne.m_fIsWizard = fAsWizard; wwOne.m_pPropSheet = &propSheet; wwTwo.m_pRule = pRule; wwTwo.m_szMBPath = m_szMBPath; wwTwo.m_fIsWizard = fAsWizard; wwTwo.m_pPropSheet = &propSheet; wwThree.m_pRule = pRule; wwThree.m_szMBPath = m_szMBPath; wwThree.m_fIsWizard = fAsWizard; wwThree.m_pPropSheet = &propSheet; // add the pages propSheet.AddPage( &wwOne ); propSheet.AddPage( &wwTwo ); propSheet.AddPage( &wwThree ); // turn it into a wizard if necessary if ( fAsWizard ) propSheet.SetWizardMode(); // set the title of the wizard/tabbed dialog thing CString szTitle; szTitle.LoadString( IDS_WILDWIZ_TITLE ); propSheet.SetTitle( szTitle ); // turn on help propSheet.m_psh.dwFlags |= PSH_HASHELP; wwOne.m_psp.dwFlags |= PSP_HASHELP; wwTwo.m_psp.dwFlags |= PSP_HASHELP; wwThree.m_psp.dwFlags |= PSP_HASHELP; // run the wizard and return if it ended with IDOK INT_PTR id = propSheet.DoModal(); return ( (id == IDOK) || (id == ID_WIZFINISH) ); /* CEditWildcardRule ruleDlg; // prepare ruleDlg.m_pRule = pRule; ruleDlg.m_szMBPath = m_szMBPath; // run the dialog and return if it ended with IDOK return (ruleDlg.DoModal() == IDOK); */ } //--------------------------------------------------------------------------- // Yeah! the CEdit11Mappings works equally well for multiple rules! - just // some modifications in this routine! BOOL CMapWildcardsPge::EditMultipleRules() { CEdit11Mappings mapdlg; CCertMapRule* pRule; BOOL fSetInitialState = FALSE; BOOL fEnable; // scan the list of seleted items for the proper initial enable button state // loop through the selected items, setting each one's mapping int iList = -1; while( (iList = m_clistctrl_list.GetNextItem( iList, LVNI_SELECTED )) >= 0 ) { // get the mapper index for the item // IA64 - this is OK to cast to DWORD as it is just an index DWORD iMapper = (DWORD)m_clistctrl_list.GetItemData( iList ); // get the mapping item for updating purposes pRule = m_mapper.GetRule( iMapper ); if ( !pRule ) { AfxMessageBox( IDS_ERR_ACCESS_MAPPING ); break; } // get the enable state of the mapping fEnable = pRule->GetRuleEnabled(); // if this is the first time, just set the initial state if ( !fSetInitialState ) { mapdlg.m_int_enable = fEnable; fSetInitialState = TRUE; } else { // if it is different, then go indeterminate and break if ( fEnable != mapdlg.m_int_enable ) { mapdlg.m_int_enable = 2; break; } } } // // ANSI/UNICODE conversion - RonaldM // USES_CONVERSION; // run the mapping dialog if ( mapdlg.DoModal() == IDOK ) { // loop through the selected items, setting each one's mapping int iList = -1; while( (iList = m_clistctrl_list.GetNextItem( iList, LVNI_SELECTED )) >= 0 ) { // get the mapper index for the item // IA64 - this is OK to cast to DWORD as it is just an index DWORD iMapper = (DWORD)m_clistctrl_list.GetItemData( iList ); // get the mapping item for updating purposes pRule = m_mapper.GetRule( iMapper ); if ( !pRule ) { AfxMessageBox( IDS_ERR_ACCESS_MAPPING ); break; } // set the enable flag if requested switch ( mapdlg.m_int_enable ) { case 0: // disable pRule->SetRuleEnabled( FALSE ); break; case 1: // enable pRule->SetRuleEnabled( TRUE ); break; } // set the NT account field of the mapping object pRule->SetRuleAccount( T2A ((LPTSTR)(LPCTSTR)mapdlg.m_sz_accountname) ); // update it in the list control too UpdateRuleInDispList( iList, pRule ); } // activate the apply button SetModified(); m_fDirty = TRUE; // return true because the user said "OK" return TRUE; } // return FALSE because the user did not say "OK" return FALSE; } //--------------------------------------------------------------------------- void CMapWildcardsPge::EnableDependantButtons() { // the whole purpose of this routine is to gray or activate // the edit and delete buttons depending on whether or not anything // is selected. So start by getting the selection count UINT cItemsSel = m_clistctrl_list.GetSelectedCount(); // if there is only one item selected, then possibly activate the up/down buttons if ( cItemsSel == 1 ) { m_cbutton_up.EnableWindow( TRUE ); m_cbutton_down.EnableWindow( TRUE ); } else { m_cbutton_up.EnableWindow( FALSE ); m_cbutton_down.EnableWindow( FALSE ); } // now the more general case of multiple selections if ( cItemsSel > 0 ) { // there are items selected m_cbutton_editrule.EnableWindow( TRUE ); m_cbutton_delete.EnableWindow( TRUE ); } else { // nope. Nothing selected m_cbutton_editrule.EnableWindow( FALSE ); m_cbutton_delete.EnableWindow( FALSE ); } // always enable the add button m_cbutton_add.EnableWindow( TRUE ); } ///////////////////////////////////////////////////////////////////////////// // CMapWildcardsPge message handlers //--------------------------------------------------------------------------- BOOL CMapWildcardsPge::OnApply() { BOOL f; CStoreXBF xbf; METADATA_HANDLE hm; // if no changes have been made, then don't do anything if ( !m_fDirty ) return TRUE; UpdateData( TRUE ); CWaitCursor wait; // set the current value of enable into place // get the globals object CCertGlobalRuleInfo* pGlob = m_mapper.GetGlobalRulesInfo(); pGlob->SetRulesEnabled( m_bool_enable ); // serialize the reference to the mapper itself f = m_mapper.Serialize( &xbf ); // before messing with the metabase, prepare the strings we will need CString szBasePath; CString szRelativePath; CString szObjectPath; if( !m_fIsIIS6 ) { szBasePath = m_szMBPath; szRelativePath = MB_EXTEND_KEY; szObjectPath = m_szMBPath + _T('/') + szRelativePath; } else { // // On IIS6 and higher the CertW mapping info is saved // directly under the site node // szBasePath = m_szMBPath; szRelativePath = ""; szObjectPath = m_szMBPath; } // prepare the metabase wrapper CWrapMetaBase mbWrap; f = mbWrap.FInit(m_pMB); // attempt to open the object we want to store into f = mbWrap.Open( szObjectPath, METADATA_PERMISSION_WRITE ); // if that did not work, we need to add the object if ( !f ) { // if szRelativePath is empty then fail right away // because there is no new node to be added if ( szRelativePath.IsEmpty() ) { if ( !f ) { AfxMessageBox(IDS_ERR_ACCESS_MAPPING); return FALSE; } } // need a slash after the namespace extention now szBasePath += _T('/'); // open the base object f = mbWrap.Open( szBasePath, METADATA_PERMISSION_WRITE ); if ( !f ) { AfxMessageBox(IDS_ERR_ACCESS_MAPPING); return FALSE; } // add the object we want f = mbWrap.AddObject( szRelativePath ); if ( !f ) { AfxMessageBox(IDS_ERR_ACCESS_MAPPING); mbWrap.Close(); return FALSE; } // close the base object f = mbWrap.Close(); // attempt to open the object we want to store into f = mbWrap.Open( szObjectPath, METADATA_PERMISSION_WRITE ); } // set the data into place in the object - If we were able to open it if ( f ) { mbWrap.SetData( _T(""), MD_SERIAL_CERTW, IIS_MD_UT_SERVER, BINARY_METADATA, xbf.GetBuff(), xbf.GetUsed(), METADATA_SECURE ); } // close the object f = mbWrap.Close(); // save the changes to the metabase f = mbWrap.Save(); // tell the persistence object to tuck away the reference so that we may find it later // f = m_persist.FSave( xbf.GetBuff(), xbf.GetUsed() ); // deactivate the apply button SetModified( FALSE ); m_fDirty = FALSE; // return f; return TRUE; } //--------------------------------------------------------------------------- void CMapWildcardsPge::OnMove( int delta ) { int iList; ASSERT( delta != 0 ); // make sure there is only one item selected ASSERT( m_clistctrl_list.GetSelectedCount() == 1 ); // Get the list index of the item in question. // this is also the index into the rule order array iList = m_clistctrl_list.GetNextItem( -1, LVNI_SELECTED ); // get the globals object CCertGlobalRuleInfo* pGlob = m_mapper.GetGlobalRulesInfo(); // get the number of rules (actually its a number of rule order - but they are the same thing) int cbRules = pGlob->GetRuleOrderCount(); // test against the edge conditions if ( ((iList == 0) && (delta < 0)) | ((iList == (cbRules - 1)) && (delta > 0)) ) return; // get the pointer to the order array DWORD * pOrder = pGlob->GetRuleOrderArray(); // calculate the new position in the array int iNewPosition = iList + delta; // store away the mapper's iIndex (not the position) of the item UINT iIndex = pOrder[iList]; // swap the positions DWORD itemp = pOrder[iNewPosition]; pOrder[iNewPosition] = pOrder[iList]; pOrder[iList] = itemp; ASSERT( pOrder[iNewPosition] == iIndex ); // unfortunately, we can't just do that with the display list. We have to remove the // the item, then re-insert it. Its a flaw in the CListCtrl object. Arg. // we have to get the item too CCertMapRule* pRule = m_mapper.GetRule( iIndex ); // delete the item from the display list m_clistctrl_list.DeleteItem( iList ); // re-insert it int iNew = AddRuleToList( pRule, iIndex, iNewPosition ); // make sure it is visible in the list m_clistctrl_list.EnsureVisible( iNew, FALSE ); // finally, because its been removed and re-inserted, we need to // re-select it as well - CListCtrl is such a pain at this LV_ITEM lv; ZeroMemory( &lv, sizeof(lv) ); lv.mask = LVIF_STATE; lv.iItem = iNew; lv.state = LVIS_SELECTED; lv.stateMask = LVIS_SELECTED; m_clistctrl_list.SetItem( &lv ); // activate the apply button SetModified(); m_fDirty = TRUE; } //--------------------------------------------------------------------------- void CMapWildcardsPge::OnMoveDown() { OnMove( 1 ); } //--------------------------------------------------------------------------- void CMapWildcardsPge::OnMoveUp() { OnMove( -1 ); } //--------------------------------------------------------------------------- void CMapWildcardsPge::OnAdd() { CHAR sz[256]; // create the new rule CCertMapRule * pNewRule = new CCertMapRule(); if (pNewRule == NULL) return; // give the new rule some defaults LoadStringA(::AfxGetInstanceHandle(), IDS_DEFAULT_RULE, sz, 255 ); pNewRule->SetRuleName( sz ); pNewRule->SetRuleEnabled( TRUE ); // Edit the rule. If it fails, remove it from the list if ( !EditOneRule( pNewRule, TRUE ) ) { // kill the rule and return delete pNewRule; return; } // make a new mapper & get its index DWORD iNewRule = m_mapper.AddRule( pNewRule ); // add the rule to the end of the display list. - It is added to the // end of the rule list by default AddRuleToList( pNewRule, iNewRule ); // make sure it is visible in the list m_clistctrl_list.EnsureVisible( iNewRule, FALSE ); // activate the apply button SetModified(); m_fDirty = TRUE; } //--------------------------------------------------------------------------- void CMapWildcardsPge::OnDelete() { // ask the user to confirm this decision if ( AfxMessageBox(IDS_CONFIRM_DELETE, MB_OKCANCEL) != IDOK ) return; CWaitCursor wait; // loop through the selected items, setting each one's mapping int iList = -1; while( (iList = m_clistctrl_list.GetNextItem( -1, LVNI_SELECTED )) >= 0 ) { // get the mapper index for the item // IA64 - this is OK to cast to DWORD as it is just an index DWORD iMapper = (DWORD)m_clistctrl_list.GetItemData( iList ); // delete the mapping from the mapper m_mapper.DeleteRule( iMapper ); // delete the entry from the list box m_clistctrl_list.DeleteItem( iList ); // because the index in the mapper for all the items below this // one changes when it is deleted, we must go and fix them all. DWORD numItems = m_clistctrl_list.GetItemCount(); for ( DWORD iFix = iList; iFix < numItems; iFix++ ) { // get the mapper index for the item to be fixed // IA64 - this is OK to cast to DWORD as it is just an index iMapper = (DWORD)m_clistctrl_list.GetItemData( iFix ); // decrement it to reflect the change iMapper--; // put it back. m_clistctrl_list.SetItemData( iFix, iMapper ); } } // activate the apply button SetModified(); m_fDirty = TRUE; } //--------------------------------------------------------------------------- void CMapWildcardsPge::OnEdit() { int iList; DWORD iRule; CCertMapRule* pUpdateRule; // what happens here depends on if just one mapping is selected, or many switch( m_clistctrl_list.GetSelectedCount() ) { case 0: // do nothing - should not get here because button grays out ASSERT( FALSE ); break; case 1: // get the mapping for update and run single edit dialog // get index of the selected list item iList = m_clistctrl_list.GetNextItem( -1, LVNI_SELECTED ); ASSERT( iList >= 0 ); // get the mapper index for the item // IA64 - this is OK to cast to DWORD as it is just an index iRule = (DWORD)m_clistctrl_list.GetItemData( iList ); // get the mapping item for updating purposes pUpdateRule = m_mapper.GetRule( iRule ); if ( !pUpdateRule ) { AfxMessageBox( IDS_ERR_ACCESS_MAPPING ); break; } // edit the mapping, update it if successful, delete if not if ( EditOneRule(pUpdateRule) ) { UpdateRuleInDispList( iList, pUpdateRule ); // activate the apply button SetModified(); m_fDirty = TRUE; } break; default: // run the multi edit dialog EditMultipleRules(); break; } } //--------------------------------------------------------------------------- void CMapWildcardsPge::OnItemchangedList(NMHDR* pNMHDR, LRESULT* pResult) { NM_LISTVIEW* pNMListView = (NM_LISTVIEW*)pNMHDR; *pResult = 0; // enable the correct items EnableDependantButtons(); } //--------------------------------------------------------------------------- void CMapWildcardsPge::OnDblclkList(NMHDR* pNMHDR, LRESULT* pResult) { *pResult = 0; // if something in the list was double clicked, edit it if ( m_clistctrl_list.GetSelectedCount() > 0 ) OnEdit(); } //--------------------------------------------------------------------------- void CMapWildcardsPge::OnEnable() { // activate the apply button SetModified(); m_fDirty = TRUE; }