1 // CSuperComboBox.cpp : implementation file
5 #include "SuperComboBox.h"
7 #include "DropHandler.h"
9 // Wrap placement new to avoid the need to temporarily #undef new
11 T &placement_cast(void *p)
20 #define DEF_MAXSIZE 20 // default maximum items to retain in each SuperComboBox
23 /////////////////////////////////////////////////////////////////////////////
26 HIMAGELIST CSuperComboBox::m_himlSystem = nullptr;
28 CSuperComboBox::CSuperComboBox()
29 : m_pDropHandler(nullptr)
30 , m_bInEditchange(false)
31 , m_bDoComplete(false)
32 , m_bAutoComplete(false)
33 , m_bHasImageList(false)
34 , m_bComboBoxEx(false)
35 , m_bExtendedFileNames(false)
36 , m_bCanBeEmpty(false)
37 , m_nMaxItems(DEF_MAXSIZE)
41 // Initialize OLE libraries if not yet initialized
42 m_bMustUninitOLE = false;
43 _AFX_THREAD_STATE* pState = AfxGetThreadState();
44 if (!pState->m_bNeedTerm)
46 SCODE sc = ::OleInitialize(nullptr);
48 AfxMessageBox(_T("OLE initialization failed. Make sure that the OLE libraries are the correct version"));
50 m_bMustUninitOLE = true;
54 CSuperComboBox::~CSuperComboBox()
56 // Uninitialize OLE support
61 BEGIN_MESSAGE_MAP(CSuperComboBox, CComboBoxEx)
62 //{{AFX_MSG_MAP(CSuperComboBox)
63 ON_CONTROL_REFLECT_EX(CBN_EDITCHANGE, OnEditchange)
64 ON_CONTROL_REFLECT_EX(CBN_SETFOCUS, OnSetfocus)
68 ON_NOTIFY_REFLECT(CBEN_GETDISPINFO, OnGetDispInfo)
72 /////////////////////////////////////////////////////////////////////////////
73 // CSuperComboBox message handlers
75 void CSuperComboBox::PreSubclassWindow()
77 CComboBoxEx::PreSubclassWindow();
78 m_pDropHandler = new DropHandler(std::bind(&CSuperComboBox::OnDropFiles, this, std::placeholders::_1));
79 RegisterDragDrop(m_hWnd, m_pDropHandler);
81 TCHAR szClassName[256];
82 GetClassName(m_hWnd, szClassName, sizeof(szClassName)/sizeof(szClassName[0]));
83 if (lstrcmpi(_T("ComboBoxEx32"), szClassName) == 0)
88 * @brief Sets additional state for handling Extended Length file names.
90 void CSuperComboBox::SetFileControlStates(bool bCanBeEmpty /*= false*/, int nMaxItems /*= -1*/)
92 ASSERT(m_bComboBoxEx);
94 m_bExtendedFileNames = true;
95 m_bCanBeEmpty = bCanBeEmpty;
97 m_nMaxItems = nMaxItems;
101 * @brief Adds a string to the list box of a combo box
102 * @param lpszItem Pointer to the null-terminated string that is to be added.
104 int CSuperComboBox::AddString(LPCTSTR lpszItem)
106 return InsertString(GetCount(), lpszItem);
110 * @brief Inserts a string into the list box of a combo box.
111 * @param nIndex The zero-based index to the position in the list box that receives the string.
112 * @param lpszItem Pointer to the null-terminated string that is to be added.
114 int CSuperComboBox::InsertString(int nIndex, LPCTSTR lpszItem)
118 CString sShortName; // scoped to remain valid for calling CComboBoxEx::InsertItem()
119 if (m_bExtendedFileNames)
121 if (nIndex >= static_cast<int>(m_sFullStateText.size()))
122 m_sFullStateText.resize(nIndex + 10);
123 sShortName = m_sFullStateText[nIndex] = lpszItem;
125 const int nPartLen = 72;
126 if (sShortName.GetLength() > (nPartLen*2+8))
128 if (sShortName.Left(4) == _T("\\\\?\\"))
129 sShortName.Delete(0, 4);
131 if (sShortName.Left(8) == _T("\\\\?\\UNC\\"))
132 sShortName.Delete(1, 6);
133 CString sL = sShortName.Left(nPartLen);
134 int nL = sL.ReverseFind(_T('\\'));
135 if (nL > 0) sL = sL.Left(nL+1);
137 CString sR = sShortName.Right(nPartLen);
138 int nR = sR.Find(_T('\\'));
139 if (nR > 0) sR = sR.Right(sR.GetLength() - nR);
141 sShortName = sL + _T(" ... ") + sR;
142 lpszItem = (LPCTSTR)sShortName;
145 COMBOBOXEXITEM cbitem = {0};
146 cbitem.mask = CBEIF_TEXT |
147 (m_bHasImageList ? CBEIF_IMAGE|CBEIF_SELECTEDIMAGE : 0);
148 cbitem.pszText = (LPTSTR)lpszItem;
149 cbitem.cchTextMax = (int)_tcslen(lpszItem);
150 cbitem.iItem = nIndex;
151 cbitem.iImage = I_IMAGECALLBACK;
152 cbitem.iSelectedImage = I_IMAGECALLBACK;
153 return CComboBoxEx::InsertItem(&cbitem);
157 return CComboBox::InsertString(nIndex, lpszItem);
161 int CSuperComboBox::DeleteString(int nIndex)
163 if (m_bComboBoxEx && m_bExtendedFileNames &&
164 nIndex >= 0 && nIndex < static_cast<int>(m_sFullStateText.size()))
166 m_sFullStateText.erase(m_sFullStateText.begin() + nIndex);
168 return CComboBoxEx::DeleteString(nIndex);
171 int CSuperComboBox::FindString(int nStartAfter, LPCTSTR lpszString) const
176 ASSERT(m_bExtendedFileNames);
177 CString sSearchString = lpszString;
178 int nSearchStringLen = sSearchString.GetLength();
179 if (nSearchStringLen <= 0)
181 int nLimit = static_cast<int>(m_sFullStateText.size());
182 for (int i = nStartAfter+1; i < nLimit; i++)
184 CString sListString = m_sFullStateText[i];
185 int nListStringLen = sListString.GetLength();
186 if (nSearchStringLen <= nListStringLen && sSearchString.CompareNoCase(sListString.Left(nSearchStringLen))==0)
193 return CComboBox::FindString(nStartAfter, lpszString);
198 * @brief Gets the system image list and attaches the image list to a combo box control.
200 bool CSuperComboBox::AttachSystemImageList()
202 ASSERT(m_bComboBoxEx);
203 if (m_himlSystem==nullptr)
205 SHFILEINFO sfi = {0};
206 m_himlSystem = (HIMAGELIST)SHGetFileInfo(_T(""), 0,
207 &sfi, sizeof(sfi), SHGFI_SMALLICON | SHGFI_SYSICONINDEX);
208 if (m_himlSystem==nullptr)
211 SetImageList(CImageList::FromHandle(m_himlSystem));
212 m_bHasImageList = true;
216 void CSuperComboBox::LoadState(LPCTSTR szRegSubKey)
220 int cnt = AfxGetApp()->GetProfileInt(szRegSubKey, _T("Count"), 0);
222 for (int i=0; i < cnt && idx < m_nMaxItems; i++)
225 s2.Format(_T("Item_%d"), i);
226 s = AfxGetApp()->GetProfileString(szRegSubKey, s2);
227 if (FindStringExact(-1, s) == CB_ERR && !s.IsEmpty())
235 bool bIsEmpty = (m_bCanBeEmpty ? (AfxGetApp()->GetProfileInt(szRegSubKey, _T("Empty"), FALSE) == TRUE) : false);
243 if (m_bExtendedFileNames)
244 GetEditCtrl()->SetWindowText(m_sFullStateText[0]);
249 void CSuperComboBox::GetLBText(int nIndex, CString &rString) const
251 ASSERT(::IsWindow(m_hWnd));
253 if (m_bExtendedFileNames)
255 rString = m_sFullStateText[nIndex];
259 CComboBoxEx::GetLBText(nIndex, rString.GetBufferSetLength(GetLBTextLen(nIndex)));
260 rString.ReleaseBuffer();
264 int CSuperComboBox::GetLBTextLen(int nIndex) const
266 if (m_bExtendedFileNames)
268 return m_sFullStateText[nIndex].GetLength();
272 return CComboBoxEx::GetLBTextLen(nIndex);
277 * @brief Saves strings in combobox.
278 * This function saves strings in combobox, in editbox and in dropdown.
279 * Whitespace characters are stripped from begin and end of the strings
280 * before saving. Empty strings are not saved. So strings which have only
281 * whitespace characters aren't save either.
282 * @param [in] szRegSubKey Registry subkey where to save strings.
283 * @param [in] bCanBeEmpty
284 * @param [in] nMaxItems Max number of strings to save.
286 void CSuperComboBox::SaveState(LPCTSTR szRegSubKey)
290 GetEditCtrl()->GetWindowText(strItem);
292 GetWindowText(strItem);
297 if (!strItem.IsEmpty())
299 AfxGetApp()->WriteProfileString(szRegSubKey, _T("Item_0"), strItem);
303 int cnt = GetCount();
304 for (int i=0; i < cnt && idx < m_nMaxItems; i++)
310 if (s != strItem && !s.IsEmpty())
313 s2.Format(_T("Item_%d"), idx);
314 AfxGetApp()->WriteProfileString(szRegSubKey, s2, s);
318 AfxGetApp()->WriteProfileInt(szRegSubKey, _T("Count"), idx);
321 AfxGetApp()->WriteProfileInt(szRegSubKey, _T("Empty"), strItem.IsEmpty());
324 BOOL CSuperComboBox::OnEditchange()
328 // Trigger a WM_WINDOWPOSCHANGING to help the client area receive an update trough WM_DRAWITEM
329 SetWindowPos(NULL, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_NOREDRAW | SWP_NOACTIVATE);
332 // bail if not auto completing
336 int length = GetWindowTextLength();
344 m_bInEditchange = true;
346 // Get the text in the edit box
350 // get the current selection
351 DWORD sel = GetEditSel();
352 int start = (short)LOWORD(sel), end = (short)HIWORD(sel);
354 // look for the string that is prefixed by the typed text
355 int idx = FindString(-1, s);
358 // set the new string
360 GetLBText(idx, strNew);
361 SetWindowText(strNew);
364 // select the text after our typing
365 if (sel == CB_ERR || end >= length)
371 // get the caret back in the right spot
373 GetEditCtrl()->SetSel(start, end);
375 CComboBox::SetEditSel(start, end);
377 m_bInEditchange = false;
382 BOOL CSuperComboBox::OnSetfocus()
385 GetEditCtrl()->SetModify(FALSE);
390 BOOL CSuperComboBox::PreTranslateMessage(MSG* pMsg)
392 if (pMsg->message == WM_KEYDOWN)
394 int nVirtKey = (int) pMsg->wParam;
395 // If Shift+Del pressed when dropdown is open, delete selected item
396 // from dropdown list
397 if (GetAsyncKeyState(VK_SHIFT))
399 if (GetDroppedState() && nVirtKey == VK_DELETE)
401 int cursel = GetCurSel();
402 if (cursel != CB_ERR)
404 DeleteString(cursel);
405 if (cursel >= GetCount())
406 cursel = GetCount() - 1;
410 return FALSE; // No need to further handle this message
415 m_bDoComplete = true;
417 if (nVirtKey == VK_DELETE || nVirtKey == VK_BACK)
418 m_bDoComplete = false;
422 return CComboBoxEx::PreTranslateMessage(pMsg);
425 void CSuperComboBox::SetAutoComplete(INT nSource)
429 case AUTO_COMPLETE_DISABLED:
430 m_bAutoComplete = false;
433 case AUTO_COMPLETE_FILE_SYSTEM:
435 // Disable the build-in auto-completion and use the Windows
436 // shell functionality.
437 m_bAutoComplete = false;
439 // ComboBox's edit control is always 1001.
440 CWnd *pWnd = m_bComboBoxEx ? this->GetEditCtrl() : GetDlgItem(1001);
441 ASSERT(pWnd != nullptr);
442 SHAutoComplete(pWnd->m_hWnd, SHACF_FILESYSTEM);
446 case AUTO_COMPLETE_RECENTLY_USED:
447 m_bAutoComplete = true;
451 ASSERT(!"Unknown AutoComplete source.");
452 m_bAutoComplete = false;
456 void CSuperComboBox::ResetContent()
458 if (m_bExtendedFileNames)
460 m_sFullStateText.resize(m_nMaxItems);
461 for (int i = 0; i < m_nMaxItems; i++)
463 m_sFullStateText[i].Empty();
466 CComboBoxEx::ResetContent();
469 int CSuperComboBox::OnCreate(LPCREATESTRUCT lpCreateStruct)
471 if (CComboBoxEx::OnCreate(lpCreateStruct) == -1)
474 m_pDropHandler = new DropHandler(std::bind(&CSuperComboBox::OnDropFiles, this, std::placeholders::_1));
475 RegisterDragDrop(m_hWnd, m_pDropHandler);
479 void CSuperComboBox::OnDestroy(void)
481 if (m_pDropHandler != nullptr)
482 RevokeDragDrop(m_hWnd);
485 /////////////////////////////////////////////////////////////////////////////
487 // OnDropFiles code from CDropEdit
488 // Copyright 1997 Chris Losinger
490 // shortcut expansion code modified from :
491 // CShortcut, 1996 Rob Warner
493 void CSuperComboBox::OnDropFiles(const std::vector<String>& files)
495 GetParent()->SendMessage(WM_COMMAND, GetDlgCtrlID() +
496 (CBN_EDITUPDATE << 16), (LPARAM)m_hWnd);
497 SetWindowText(files[0].c_str());
498 GetParent()->SendMessage(WM_COMMAND, GetDlgCtrlID() +
499 (CBN_EDITCHANGE << 16), (LPARAM)m_hWnd);
502 static DWORD WINAPI SHGetFileInfoThread(LPVOID pParam)
504 CString &sPath = reinterpret_cast<CString &>(pParam);
505 SHFILEINFO sfi = {0};
506 // If SHGetFileInfo() fails, intentionally leave sfi.iIcon as 0 (indicating
507 // a file of inspecific type) so as to not obstruct CBEIF_DI_SETITEM logic.
508 if (!sPath.IsEmpty())
510 if (SUCCEEDED(CoInitialize(nullptr)))
512 SHGetFileInfo(sPath, 0, &sfi, sizeof(sfi), SHGFI_SYSICONINDEX);
520 static int GetFileTypeIconIndex(LPVOID pParam)
522 CString &sText = reinterpret_cast<CString &>(pParam);
523 DWORD dwIconIndex = 0;
524 bool isNetworkDrive = false;
525 if (sText.GetLength() >= 2 && (sText[1] == L'\\'))
527 if (sText.GetLength() > 4 && sText.Left(4) == L"\\\\?\\")
528 if (sText.GetLength() > 8 && sText.Left(8) == L"\\\\?\\UNC\\")
529 isNetworkDrive = true;
531 isNetworkDrive = false; // Just a Long File Name indicator
533 isNetworkDrive = true;
536 if (sText.GetLength() >= 3 && GetDriveType(sText.Left(3)) == DRIVE_REMOTE)
537 isNetworkDrive = true; // Drive letter, but mapped to Remote UNC device.
539 // Unless execution drops into the final else block,
540 // SHGetFileInfoThread() takes ownership of, and will eventually trash sText
543 dwIconIndex = SHGetFileInfoThread(pParam);
546 if (HANDLE hThread = CreateThread(nullptr, 0, SHGetFileInfoThread, pParam, 0, nullptr))
548 // The path is a network path.
549 // Try to get the index of a system image list icon, with 1-sec timeout.
551 DWORD dwResult = WaitForSingleObject(hThread, 1000);
552 if (dwResult == WAIT_OBJECT_0)
554 GetExitCodeThread(hThread, &dwIconIndex);
556 CloseHandle(hThread);
560 // Ownership of sText was retained, so trash it here
563 return static_cast<int>(dwIconIndex);
566 void CSuperComboBox::OnDrawItem(int nIDCtl, LPDRAWITEMSTRUCT lpDrawItemStruct)
568 if ((lpDrawItemStruct->itemState & ODS_COMBOBOXEDIT) != 0 && m_bHasImageList)
571 CString &sText = placement_cast<CString>(&pvText);
572 CEdit *const pEdit = GetEditCtrl();
573 if (!pEdit->GetModify() || GetFocus() != pEdit)
574 GetWindowText(sText);
575 int iIcon = GetFileTypeIconIndex(pvText);
576 ImageList_DrawEx(m_himlSystem, iIcon, lpDrawItemStruct->hDC,
577 lpDrawItemStruct->rcItem.left, lpDrawItemStruct->rcItem.top,
578 0, 0, GetSysColor(COLOR_WINDOW), CLR_NONE, ILD_NORMAL);
581 CComboBoxEx::OnDrawItem(nIDCtl, lpDrawItemStruct);
585 * @brief A message handler for CBEN_GETDISPINFO message
587 void CSuperComboBox::OnGetDispInfo(NMHDR *pNotifyStruct, LRESULT *pResult)
589 NMCOMBOBOXEX *pDispInfo = (NMCOMBOBOXEX *)pNotifyStruct;
590 if (pDispInfo && pDispInfo->ceItem.pszText && m_bHasImageList)
592 pDispInfo->ceItem.mask |= CBEIF_DI_SETITEM;
594 GetLBText(static_cast<int>(pDispInfo->ceItem.iItem), placement_cast<CString>(&pvText));
595 int iIcon = GetFileTypeIconIndex(pvText);
596 pDispInfo->ceItem.iImage = iIcon;
597 pDispInfo->ceItem.iSelectedImage = iIcon;