//////////////////////////////////////////////////////////////////////////// // File: XHistoryCombo.cpp // Version: 2 // Created: 01-May-2002 // // Author: Paul S. Vickery // E-mail: paul@vickeryhome.freeserve.co.uk // // Implementation of CHistoryCombo which incorporates functionality to help // with Loading and Saving of history in a combo box // // You are free to use or modify this code, with no restrictions, other than // you continue to acknowledge me as the original author in this source code, // or any code derived from it. // // If you use this code, or use it as a base for your own code, it would be // nice to hear from you simply so I know it's not been a waste of time! // // Copyright (c) 2001-2002 Paul S. Vickery // //////////////////////////////////////////////////////////////////////////// // Version History: // // Version 2 - 01-May-2002 // ======================= // Produced new version with changes as below: // * removed CBS_SORT on creation if specified // * added option to allow the sort style to be set if required // * fixed SetMaxHistoryItems, so it removes old entries from the list to // ensure that there are no more than the maximum. Also made SaveHistory // remove redundant profile entries above the maximum. // * use WriteProfileString to remove profile entries rather than CRegKey. // // Version 1 - 12-Apr-2001 // ======================= // Initial version // //////////////////////////////////////////////////////////////////////////// // PLEASE LEAVE THIS HEADER INTACT //////////////////////////////////////////////////////////////////////////// // // Modified 20-Jun-2003 by Hans Dietrich - see "+++hd" lines for details. // Changed name to XHistoryCombo to avoid confusion with original. // //////////////////////////////////////////////////////////////////////////// #include "stdafx.h" #include "XHistoryCombo.h" #include "io.h" // needed for _taccess //+++hd #if defined(_DEBUG) && defined(_CRTDBG_MAP_ALLOC) #define new DEBUG_NEW #endif #ifndef __noop #if _MSC_VER < 1300 #define __noop ((void)0) #endif #endif #undef TRACE #define TRACE __noop //============================================================================= // if you want to see the TRACE output, uncomment this line: //#include "XTrace.h" //============================================================================= #define MAX_HISTORY_ITEMS 100 #define MAX_DROPDOWN_ITEMS 10 //+++hd #define KEY_PREFIX_FORMAT _T("%s%04d") //+++hd ///////////////////////////////////////////////////////////////////////////// // CHistoryCombo CHistoryCombo::CHistoryCombo(BOOL bAllowSortStyle/*=FALSE*/) { TRACE(_T("in CHistoryCombo::CHistoryCombo\n")); m_nMaxHistoryItems = MAX_HISTORY_ITEMS; m_bSaveRestoreLastCurrent = TRUE; m_bAllowSortStyle = bAllowSortStyle; m_bCheckAccess = FALSE; //+++hd m_nDropSize = MAX_DROPDOWN_ITEMS; //+++hd } BOOL CHistoryCombo::PreCreateWindow(CREATESTRUCT& cs) { if (! m_bAllowSortStyle) // turn off CBS_SORT style cs.style &= ~CBS_SORT; return CComboBox::PreCreateWindow(cs); } void CHistoryCombo::PreSubclassWindow() { // warn if creating with CBS_SORT style // (unfortunately we can't turn it off) if (! m_bAllowSortStyle && GetStyle() & CBS_SORT) TRACE(_T("WARNING: Creating History combo with CBS_SORT style\n")); CComboBox::PreSubclassWindow(); } BEGIN_MESSAGE_MAP(CHistoryCombo, CComboBox) //{{AFX_MSG_MAP(CHistoryCombo) ON_CONTROL_REFLECT(CBN_DROPDOWN, OnDropdown) //+++hd //}}AFX_MSG_MAP END_MESSAGE_MAP() ///////////////////////////////////////////////////////////////////////////// // CHistoryCombo message handlers // this version of AddString adds a string only if it doesn't already exist // in the list, and in any case, makes sure that the string is the first // in the list (ie most recent in history) // also makes sure number of items in the list doesn't exceed the maximum allowed int CHistoryCombo::AddString(LPCTSTR lpszString) { CString sString(lpszString); // if it's not set up as a history combo then call base class if (m_sSection.IsEmpty() || m_sKeyPrefix.IsEmpty()) return CComboBox::AddString(lpszString); int nRet = -1; // don't add if already there sString.TrimLeft(); sString.TrimRight(); if (sString.IsEmpty()) { TRACE(_T("ERROR string is empty\n")); return CB_ERR; } nRet = CComboBox::InsertString(0, sString); int nIndex = FindStringExact(0, sString); if (nIndex != -1 && nIndex != 0) DeleteString(nIndex); // if we've got too many items then delete the last one // truncate list to m_nMaxHistoryItems int nNumItems = GetCount(); for (int n = m_nMaxHistoryItems; n < nNumItems; n++) DeleteString(m_nMaxHistoryItems); SetCurSel(nRet); return nRet; } // loads the history from the specified MRU list, and populates the combo list // if bSelectMostRecent is TRUE, selects the most recent into the edit control // returns the selected text // if using this overload of LoadHistory then SaveHistory will not function // but you should simply save the MRU in the normal way instead // note that the MRU should have already been read from the profile before // being passed to this function, as this function will not call MRU->ReadList() CString CHistoryCombo::LoadHistory(CRecentFileList* pListMRU, BOOL bSelectMostRecent/*=TRUE*/) { if (pListMRU == NULL) return _T(""); int nNumItems = pListMRU->GetSize(); for (int n = 0; n < nNumItems; n++) CComboBox::AddString((*pListMRU)[n]); if (bSelectMostRecent) SetCurSel(0); CString sText; GetWindowText(sText); return sText; } // loads the history from the specified profile area, and returns the // text selected // the profile area is kept so that in doesn't need to specified again // when saving the history CString CHistoryCombo::LoadHistory(LPCTSTR lpszSection, LPCTSTR lpszKeyPrefix, BOOL bSaveRestoreLastCurrent/*=TRUE*/, LPCTSTR lpszKeyCurItem/*=NULL*/) { if (lpszSection == NULL || lpszKeyPrefix == NULL || *lpszSection == _T('\0')) return _T(""); TRACE(_T("lpszSection=%s lpszKeyPrefix=%s\n"), lpszSection, lpszKeyPrefix); m_sSection = lpszSection; m_sKeyPrefix = lpszKeyPrefix; m_sKeyCurItem = lpszKeyCurItem == NULL ? _T("") : lpszKeyCurItem; m_bSaveRestoreLastCurrent = bSaveRestoreLastCurrent; CWinApp* pApp = AfxGetApp(); ASSERT(pApp); int n = 0; CString sText; do { CString sKey; sKey.Format(KEY_PREFIX_FORMAT, m_sKeyPrefix, n++); sText = pApp->GetProfileString(m_sSection, sKey); if (m_bCheckAccess) //+++hd { //+++hd if (_taccess(sText, 00) != -1) //+++hd { //+++hd if (!sText.IsEmpty()) //+++hd CComboBox::AddString(sText); //+++hd } //+++hd } //+++hd else //+++hd { if (!sText.IsEmpty()) CComboBox::AddString(sText); } } while (!sText.IsEmpty() && n < m_nMaxHistoryItems); if (m_bSaveRestoreLastCurrent) { CString sKey; if (!m_sKeyCurItem.IsEmpty()) sKey = m_sKeyCurItem; else if (m_sKeyPrefix.IsEmpty()) sKey = _T("Last"); else sKey = m_sKeyPrefix; sText = pApp->GetProfileString(m_sSection, sKey); if (!sText.IsEmpty()) { int nIndex = FindStringExact(-1, sText); if (nIndex != -1) SetCurSel(nIndex); else if (GetStyle() & CBS_DROPDOWN) SetWindowText(sText); } } return sText; } // saves the history to the profile specified when calling LoadHistory // if no profile information (ie LoadHistory() wasn't called with it) then // this function does nothing void CHistoryCombo::SaveHistory(BOOL bAddCurrentItemToHistory/*=TRUE*/) { TRACE(_T("in CHistoryCombo::SaveHistory\n")); if (m_sSection.IsEmpty()) return; CWinApp* pApp = AfxGetApp(); ASSERT(pApp); if (bAddCurrentItemToHistory) { CString sCurItem; GetWindowText(sCurItem); // trim it, so we items which differ only by a leading/trailing space sCurItem.TrimLeft(); sCurItem.TrimRight(); if (! sCurItem.IsEmpty()) AddString(sCurItem); } // save history to info cached earlier int nMax = min(GetCount(), m_nMaxHistoryItems + 1); int n = 0; for (n = 0; n < nMax; n++) { CString sKey; sKey.Format(KEY_PREFIX_FORMAT, m_sKeyPrefix, n); CString sText; GetLBText(n, sText); pApp->WriteProfileString(m_sSection, sKey, sText); //TRACE(_T("m_sSection=%s sKey=%s sText=%s\n"), m_sSection, sKey, sText); } // remove redundant items for (n = nMax; n < 1000/* prevent runaway*/; n++) { CString sKey; sKey.Format(KEY_PREFIX_FORMAT, m_sKeyPrefix, n); TRACE(_T("m_sKeyPrefix=%s\n"), m_sKeyPrefix); CString sText = pApp->GetProfileString(m_sSection, sKey); if (sText.IsEmpty()) break; pApp->WriteProfileString(m_sSection, sKey, NULL); // remove entry } if (m_bSaveRestoreLastCurrent) { CString sText; GetWindowText(sText); CString sKey; if (!m_sKeyCurItem.IsEmpty()) sKey = m_sKeyCurItem; else if (m_sKeyPrefix.IsEmpty()) sKey = _T("Last"); else sKey = m_sKeyPrefix; pApp->WriteProfileString(m_sSection, sKey, sText); } } // removes all the items from the history list, and optionally deletes // the registry items. Note that if the history list is generated from // a CRecentFileList, then registry entries will not be deleted void CHistoryCombo::ClearHistory(BOOL bDeleteRegistryEntries/*=TRUE*/) { ResetContent(); if (! m_sSection.IsEmpty() && bDeleteRegistryEntries) { // remove profile entries CWinApp* pApp = AfxGetApp(); ASSERT(pApp); CString sKey; for (int n = 0; n < 1000/* prevent runaway*/; n++) { sKey.Format(KEY_PREFIX_FORMAT, m_sKeyPrefix, n); CString sText = pApp->GetProfileString(m_sSection, sKey); if (sText.IsEmpty()) break; pApp->WriteProfileString(m_sSection, sKey, NULL); // remove entry } if (! m_sKeyCurItem.IsEmpty()) sKey = m_sKeyCurItem; else if (m_sKeyPrefix.IsEmpty()) sKey = _T("Last"); else sKey = m_sKeyPrefix; pApp->WriteProfileString(m_sSection, sKey, NULL); } } void CHistoryCombo::SetMaxHistoryItems(int nMaxItems) { m_nMaxHistoryItems = nMaxItems; // truncate list to nMaxItems int nNumItems = GetCount(); for (int n = m_nMaxHistoryItems; n < nNumItems; n++) DeleteString(m_nMaxHistoryItems); } void CHistoryCombo::StoreValue(BOOL bIgnoreIfEmpty/*=TRUE*/) { // add the current value to the list CString sValue; GetWindowText(sValue); if (bIgnoreIfEmpty && sValue.IsEmpty()) return; AddString(sValue); } // +++hd: set size of dropdown list void CHistoryCombo::OnDropdown() { CRect rect; GetWindowRect(&rect); int nHeight = rect.Height() + 2 * ::GetSystemMetrics(SM_CXBORDER); int nItemHeight = GetItemHeight(0); // m_nDropSize = No. of items in dropdown list nHeight += m_nDropSize * nItemHeight; SetWindowPos(NULL, 0, 0, rect.Width(), nHeight, SWP_NOMOVE | SWP_NOZORDER); }